Last active
July 17, 2025 23:35
-
-
Save theoknock/eac91b73056c6dfcc510b36a7fec6a1f to your computer and use it in GitHub Desktop.
Hosts the function that streams the response from a chat model to the client submitting a prompt
This file contains hidden or 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
// | |
// ContentView.swift | |
// ChatGPTCoreModel_playground | |
// | |
// Created by Xcode Developer on 7/15/25. | |
// | |
import SwiftUI | |
import FoundationModels | |
// MARK: - Model for a single queued Psalm abstract | |
struct PsalmAbstract: Identifiable { | |
let id = UUID() | |
let psalmNumber: Int | |
var response: String = "Pending..." | |
var isCompleted: Bool = false | |
} | |
// MARK: - Actor for safe queueing | |
actor PsalmQueue { | |
private(set) var items: [PsalmAbstract] = [] | |
func addPsalm(_ psalmNumber: Int) -> PsalmAbstract { | |
let abstract = PsalmAbstract(psalmNumber: psalmNumber) | |
items.append(abstract) | |
return abstract | |
} | |
func updateResponse(for id: UUID, response: String) { | |
if let index = items.firstIndex(where: { $0.id == id }) { | |
items[index].response = response | |
items[index].isCompleted = true | |
} | |
} | |
var currentItems: [PsalmAbstract] { | |
items | |
} | |
} | |
// MARK: - Main View | |
struct ContentView: View { | |
@State private var psalmNumber: Int = .zero | |
@State private var psalmNumberInput: String = String() | |
var quotedPsalmNumberInput: Binding<String> { | |
Binding<String>( | |
get: { | |
"\(psalmNumberInput)" | |
}, | |
set: { newValue in | |
if newValue.hasPrefix("\"") && newValue.hasSuffix("\"") { | |
psalmNumberInput = String(newValue.dropFirst().dropLast()) | |
} else { | |
psalmNumberInput = newValue | |
} | |
} | |
) | |
} | |
@State private var abstracts: [PsalmAbstract] = [] | |
private let queue = PsalmQueue() | |
@State private var responseStream: AbstractPsalmResponse.PartiallyGenerated? | |
var partialResponseStream: Binding<AbstractPsalmResponse.PartiallyGenerated?> { | |
Binding<AbstractPsalmResponse.PartiallyGenerated?>( | |
get: { | |
responseStream | |
}, | |
set: { newValue in | |
responseStream = newValue | |
} | |
) | |
} | |
// Timer properties for stepper acceleration | |
@State private var timer: Timer? | |
@State private var timerInterval: TimeInterval = 0.5 | |
@State private var isIncrementing: Bool = true | |
var body: some View { | |
ZStack { | |
// Linear gradient background | |
LinearGradient( | |
gradient: Gradient(colors: [ | |
Color.primary.opacity(0.25), | |
Color.accentColor.opacity(0.25) | |
]), | |
startPoint: .bottomTrailing, | |
endPoint: .topLeading | |
) | |
.ignoresSafeArea() | |
VStack(content: { | |
ZStack(alignment: (.center), content: { | |
// HStack { | |
// TextField(("Enter a psalm (1-150)..."), text: $psalmNumberInput, axis: .horizontal) | |
// .padding(.leading) | |
// Spacer() | |
// } | |
GlassEffectContainer(spacing: CGFloat.zero) { | |
HStack { | |
Button { | |
decrementPsalm() | |
} label: { | |
Image(systemName: "minus.circle") | |
.foregroundColor(Color(UIColor.white)) | |
.symbolRenderingMode(.hierarchical) | |
.font(.largeTitle) | |
.imageScale(.small) | |
.labelStyle(.iconOnly) | |
.clipShape(Circle()) | |
.padding(8) | |
.glassEffect() | |
} | |
.padding(.leading, 2) | |
.buttonStyle(PlainButtonStyle()) | |
.shadow(color: Color.white.opacity(0.5), radius: 2, x: 2, y: 2) | |
.simultaneousGesture( | |
LongPressGesture().onEnded { _ in | |
startTimer(incrementing: false) | |
} | |
) | |
.simultaneousGesture( | |
DragGesture(minimumDistance: 0).onEnded { _ in | |
stopTimer() | |
} | |
) | |
// Spacer() | |
// Number input field | |
TextField("Psalm \(psalmNumber)", text: quotedPsalmNumberInput) // "Psalm", text: $psalmNumberInput) | |
.keyboardType(.numberPad) | |
.multilineTextAlignment(.center) | |
.textFieldStyle(DefaultTextFieldStyle()) | |
.shadow(color: Color.black.opacity(0.5), radius: 2, x: 0, y: 0) | |
.onChange(of: psalmNumberInput) { oldValue, newValue in | |
let filtered = newValue.filter { "0123456789".contains($0) } | |
if let value = Int(filtered) { | |
psalmNumber = min(max(value, 1), 150) | |
} | |
psalmNumberInput = "\(psalmNumber)" | |
} | |
.font(.largeTitle) | |
.foregroundColor(Color(UIColor.white)) | |
.background(Color(UIColor.clear)) | |
.padding(4) | |
// Spacer() | |
Button { | |
incrementPsalm() | |
} label: { | |
Image(systemName: "plus.circle") | |
.padding(8) | |
.foregroundColor(Color(UIColor.white)) | |
.symbolRenderingMode(.hierarchical) | |
.font(.largeTitle) | |
.imageScale(.small) | |
.labelStyle(.iconOnly) | |
.clipShape(Circle()) | |
.glassEffect() | |
} | |
.simultaneousGesture( | |
LongPressGesture().onEnded { _ in | |
startTimer(incrementing: true) | |
} | |
) | |
.simultaneousGesture( | |
DragGesture(minimumDistance: 0).onEnded { _ in | |
stopTimer() | |
} | |
) | |
} // END OF HSTACK | |
} // END OF GLASSEFFECTCONTAINER | |
.fixedSize() | |
.glassEffect(.regular.tint(Color.accentColor.opacity(0.25))) | |
// .background(Color.init(uiColor: UIColor(white: 1.0, alpha: 0.2))) | |
// .clipShape(RoundedRectangle(cornerSize: CGSize(width: 25, height: 25), style: .continuous)) | |
// .padding(.trailing, 75) | |
// Button { | |
// dismissKeyboard() | |
// addPsalmAndRun() | |
// } label: { | |
// Image(systemName: "pencil") | |
// .padding(8) | |
// .foregroundColor(Color(UIColor.white)) | |
// .symbolRenderingMode(.monochrome) | |
// .font(.largeTitle) | |
// .imageScale(.medium) | |
// .labelStyle(.iconOnly) | |
// .clipShape(Circle()) | |
// } | |
// // .padding(.leading, 100) | |
// .glassEffect(in: .rect(cornerRadius: 25.0)) | |
// // .shadow(color: Color.white.opacity(0.5), radius: 2, x: 0, y: 0) | |
}) | |
ScrollView { | |
VStack(alignment: .leading, spacing: 16) { | |
ForEach(abstracts) { item in | |
VStack(alignment: .leading, spacing: 8) { | |
Text("Psalm \(item.psalmNumber)") | |
.font(.title2) | |
.fontWeight(.medium) | |
.padding(.vertical) | |
.frame(idealWidth: UIScreen.main.bounds.size.width, maxWidth: UIScreen.main.bounds.size.width) | |
.glassEffect(in: .rect(cornerRadius: 25.0)) | |
if item.isCompleted { | |
Text(item.response) | |
.dynamicTypeSize(DynamicTypeSize.xSmall) | |
.font(.body) | |
.frame(maxWidth: UIScreen.main.bounds.size.width) | |
} else { | |
ProgressView() | |
.progressViewStyle(CircularProgressViewStyle()) | |
} | |
} | |
.padding() | |
.background(Color.gray.opacity(0.1)) | |
.cornerRadius(28) | |
.glassEffect(in: .rect(cornerRadius: 25.0)) | |
.padding(.vertical) | |
} | |
} | |
// .padding(.bottom, 50) | |
Spacer() | |
} | |
// .frame(width: .infinity, height: .infinity) | |
// .border(Color.white.opacity(1.0), width: 0.2) | |
// .backgroundStyle(Color.white.opacity(1.0)) | |
.ignoresSafeArea() | |
}) | |
.padding() | |
.onAppear { | |
psalmNumberInput = "\(psalmNumber)" | |
Task { | |
await refreshQueue() | |
} | |
} | |
VStack { | |
Spacer() | |
HStack(alignment: .bottom, content: { | |
Spacer() | |
Text("James Alan Bush") | |
.font(.caption) | |
.foregroundColor(.primary) | |
Spacer() | |
Text("Commit ID 0863a4e") | |
.font(.caption2) | |
.foregroundColor(.secondary) | |
Spacer() | |
}) | |
} | |
// .frame(width: .infinity, height: .infinity) | |
// .border(Color.white.opacity(1.0), width: 0.2) | |
// .backgroundStyle(Color.white.opacity(1.0)) | |
} | |
} | |
private func dismissKeyboard() { | |
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) | |
} | |
// MARK: - Stepper Logic | |
private func incrementPsalm() { | |
if psalmNumber < 150 { | |
psalmNumber += 1 | |
psalmNumberInput = "\(psalmNumber)" | |
} | |
} | |
private func decrementPsalm() { | |
if psalmNumber > 1 { | |
psalmNumber -= 1 | |
psalmNumberInput = "\(psalmNumber)" | |
} | |
} | |
private func startTimer(incrementing: Bool) { | |
isIncrementing = incrementing | |
timerInterval = 0.5 | |
stopTimer() | |
timer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true) { _ in | |
if isIncrementing { | |
incrementPsalm() | |
} else { | |
decrementPsalm() | |
} | |
accelerateScrolling() | |
} | |
} | |
private func stopTimer() { | |
timer?.invalidate() | |
timer = nil | |
} | |
private func accelerateScrolling() { | |
if timerInterval > 0.1 { | |
timerInterval -= 0.05 | |
stopTimer() | |
timer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true) { _ in | |
if isIncrementing { | |
incrementPsalm() | |
} else { | |
decrementPsalm() | |
} | |
accelerateScrolling() | |
} | |
} | |
} | |
// MARK: - Add & Execute | |
private func addPsalmAndRun() { | |
Task { | |
let abstract = await queue.addPsalm(psalmNumber) | |
await refreshQueue() | |
Task.detached { | |
await runPsalmAbstract(abstract) | |
} | |
} | |
} | |
private func runPsalmAbstract(_ abstract: PsalmAbstract) async { | |
do { | |
let instructions: Instructions = Instructions(""" | |
Your instructions: | |
Your task is to write a 6-paragraph academic abstract of psalm \(abstract.psalmNumber). Following are the specifications for each paragraph, which describe some of the different ways biblical scholars and theologians analyze Scripture. | |
Paragraph 1: Start by selecting a verse or passage within psalm \(abstract.psalmNumber) that is central to its message. The response format will simply be a standard scripture quote from the King James Version (i.e., the verse(s)) and then the reference on a new line). Proceed to write paragraph 2. If you did not proceed to paragraph 2, explain why in your response. | |
Paragraph 2: Describe the purpose of psalm \(abstract.psalmNumber), explaining its spiritual intent and how it serves or helps the believer. Avoid mentioning the writer unless referring to the Psalm's direct impact on worship or spiritual life. | |
Paragraph 3: Identify and summarize the key themes found in the psalm, supported by references from the text itself. | |
Paragraph 4: Summarize any theological connections in psalm \(abstract.psalmNumber) and how these connections contribute to an understanding of God, faith, and spiritual matters. | |
Paragraph 5: Summarize any Christological connections in psalm \(abstract.psalmNumber) and how these connections contribute to an understanding of the nature and work of Christ; quote any relevant scripture found elsewhere in the Bible, if necessary. | |
Paragraph 6: Draw direct parallels to Christian teachings, using New Testament scriptures to illustrate how the message of the psalm is fulfilled or mirrored in Christ and His teachings, and give advice on how Christians today can apply the psalm's lessons in their own lives. | |
""") | |
let session: LanguageModelSession = LanguageModelSession(instructions: instructions) | |
let prompt: Prompt = Prompt(""" | |
Psalm \(abstract.psalmNumber) | |
""") | |
let stream = session.streamResponse( | |
to: prompt, | |
generating: AbstractPsalmResponse.self | |
) | |
var accumulatedContent = "" | |
for try await pstream in stream { | |
// Store the current stream state | |
await MainActor.run { | |
self.responseStream = pstream | |
} | |
// Extract the content from the stream | |
// abstractPsalmResponse is Optional<Array<String>> | |
if let partialResponse = pstream.abstractPsalmResponse { | |
// Join the array of strings into a single string | |
let content = partialResponse.joined(separator: "\n") | |
accumulatedContent = content | |
} else { | |
// If no response yet, continue to next iteration | |
continue | |
} | |
// Update the queue with the accumulated content | |
await queue.updateResponse(for: abstract.id, response: accumulatedContent) | |
// Refresh the UI | |
await refreshQueue() | |
} | |
// Final update to mark as completed | |
await queue.updateResponse(for: abstract.id, response: accumulatedContent) | |
} catch { | |
await queue.updateResponse(for: abstract.id, response: "Error: \(error.localizedDescription)") | |
} | |
await refreshQueue() | |
} | |
@MainActor | |
private func refreshQueue() async { | |
abstracts = await queue.currentItems | |
} | |
} | |
#Preview { | |
ContentView() | |
.preferredColorScheme(.dark) | |
} |
This file contains hidden or 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
private func runPsalmAbstract(_ abstract: PsalmAbstract) async { | |
do { | |
let instructions: Instructions = Instructions(""" | |
Your instructions: | |
Your task is to write a 6-paragraph academic abstract of psalm \(abstract.psalmNumber). Following are the specifications for each paragraph, which describe some of the different ways biblical scholars and theologians analyze Scripture. | |
Paragraph 1: Start by selecting a verse or passage within psalm \(abstract.psalmNumber) that is central to its message. The response format will simply be a standard scripture quote from the King James Version (i.e., the verse(s)) and then the reference on a new line). Proceed to write paragraph 2. If you did not proceed to paragraph 2, explain why in your response. | |
Paragraph 2: Describe the purpose of psalm \(abstract.psalmNumber), explaining its spiritual intent and how it serves or helps the believer. Avoid mentioning the writer unless referring to the Psalm's direct impact on worship or spiritual life. | |
Paragraph 3: Identify and summarize the key themes found in the psalm, supported by references from the text itself. | |
Paragraph 4: Summarize any theological connections in psalm \(abstract.psalmNumber) and how these connections contribute to an understanding of God, faith, and spiritual matters. | |
Paragraph 5: Summarize any Christological connections in psalm \(abstract.psalmNumber) and how these connections contribute to an understanding of the nature and work of Christ; quote any relevant scripture found elsewhere in the Bible, if necessary. | |
Paragraph 6: Draw direct parallels to Christian teachings, using New Testament scriptures to illustrate how the message of the psalm is fulfilled or mirrored in Christ and His teachings, and give advice on how Christians today can apply the psalm's lessons in their own lives. | |
""") | |
let session: LanguageModelSession = LanguageModelSession(instructions: instructions) | |
let prompt: Prompt = Prompt(""" | |
Psalm \(abstract.psalmNumber) | |
""") | |
let stream = session.streamResponse( | |
to: prompt, | |
generating: AbstractPsalmResponse.self | |
) | |
var accumulatedContent = "" | |
for try await pstream in stream { | |
// Store the current stream state | |
await MainActor.run { | |
self.responseStream = pstream | |
} | |
// Extract the content from the stream | |
// abstractPsalmResponse is Optional<Array<String>> | |
if let partialResponse = pstream.abstractPsalmResponse { | |
// Join the array of strings into a single string | |
let content = partialResponse.joined(separator: "\n") | |
accumulatedContent = content | |
} else { | |
// If no response yet, continue to next iteration | |
continue | |
} | |
// Update the queue with the accumulated content | |
await queue.updateResponse(for: abstract.id, response: accumulatedContent) | |
// Refresh the UI | |
await refreshQueue() | |
} | |
// Final update to mark as completed | |
await queue.updateResponse(for: abstract.id, response: accumulatedContent) | |
} catch { | |
await queue.updateResponse(for: abstract.id, response: "Error: \(error.localizedDescription)") | |
} | |
await refreshQueue() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment