Section 11/151 menit
11. Integrasi dengan SwiftUI
11. Integrasi dengan SwiftUI
Chat Interface Lengkap
swift
import FoundationModels
import SwiftUI
struct Message: Identifiable {
let id = UUID()
let role: Role
let content: String
enum Role {
case user, assistant
}
}
@Observable
class ChatViewModel {
var messages: [Message] = []
var isLoading = false
var inputText = ""
private var session: LanguageModelSession?
init() {
setupSession()
}
private func setupSession() {
guard case .available = SystemLanguageModel.default.availability else { return }
session = LanguageModelSession(
instructions: Instructions("Kamu adalah asisten yang membantu dan ramah.")
)
}
func sendMessage() async {
guard let session, !inputText.trimmingCharacters(in: .whitespaces).isEmpty else { return }
let userText = inputText
inputText = ""
messages.append(Message(role: .user, content: userText))
isLoading = true
do {
var assistantText = ""
messages.append(Message(role: .assistant, content: ""))
let lastIndex = messages.count - 1
let stream = session.streamResponse(to: userText)
for try await partial in stream {
assistantText = partial.content
messages[lastIndex] = Message(role: .assistant, content: assistantText)
}
} catch {
messages.append(Message(role: .assistant, content: "Terjadi error: \(error.localizedDescription)"))
}
isLoading = false
}
}
struct ChatView: View {
@State private var viewModel = ChatViewModel()
var body: some View {
NavigationStack {
VStack(spacing: 0) {
ScrollViewReader { proxy in
ScrollView {
LazyVStack(spacing: 12) {
ForEach(viewModel.messages) { message in
MessageBubble(message: message)
.id(message.id)
}
if viewModel.isLoading && viewModel.messages.last?.role == .assistant {
EmptyView()
}
}
.padding()
}
.onChange(of: viewModel.messages.count) {
if let last = viewModel.messages.last {
withAnimation {
proxy.scrollTo(last.id, anchor: .bottom)
}
}
}
}
Divider()
InputBar(viewModel: viewModel)
}
.navigationTitle("AI Assistant")
}
}
}
struct MessageBubble: View {
let message: Message
var body: some View {
HStack {
if message.role == .user { Spacer() }
Text(message.content)
.padding(12)
.background(message.role == .user ? Color.blue : Color(.secondarySystemBackground))
.foregroundStyle(message.role == .user ? .white : .primary)
.clipShape(RoundedRectangle(cornerRadius: 16))
if message.role == .assistant { Spacer() }
}
}
}
struct InputBar: View {
var viewModel: ChatViewModel
var body: some View {
HStack(spacing: 12) {
TextField("Ketik pesan...", text: $viewModel.inputText, axis: .vertical)
.textFieldStyle(.roundedBorder)
.lineLimit(1...5)
Button {
Task { await viewModel.sendMessage() }
} label: {
Image(systemName: "arrow.up.circle.fill")
.font(.title2)
}
.disabled(viewModel.isLoading || viewModel.inputText.trimmingCharacters(in: .whitespaces).isEmpty)
}
.padding()
}
}