Files
oai-swift/oAI/Views/Main/ChatView.swift

83 lines
2.6 KiB
Swift

//
// ChatView.swift
// oAI
//
// Main chat interface
//
import SwiftUI
struct ChatView: View {
@Environment(ChatViewModel.self) var viewModel
let onModelSelect: () -> Void
let onProviderChange: (Settings.Provider) -> Void
var body: some View {
@Bindable var viewModel = viewModel
VStack(spacing: 0) {
// Header
HeaderView(
provider: viewModel.currentProvider,
model: viewModel.selectedModel,
stats: viewModel.sessionStats,
onlineMode: viewModel.onlineMode,
mcpEnabled: viewModel.mcpEnabled,
mcpStatus: viewModel.mcpStatus,
onModelSelect: onModelSelect,
onProviderChange: onProviderChange
)
// Messages
ScrollViewReader { proxy in
ScrollView {
LazyVStack(alignment: .leading, spacing: 12) {
ForEach(viewModel.messages) { message in
MessageRow(message: message)
.id(message.id)
}
// Invisible bottom anchor for auto-scroll
Color.clear
.frame(height: 1)
.id("bottom")
}
.padding()
}
.background(Color.oaiBackground)
.onChange(of: viewModel.messages.count) {
withAnimation {
proxy.scrollTo("bottom", anchor: .bottom)
}
}
.onChange(of: viewModel.messages.last?.content) {
// Auto-scroll as streaming content arrives
if viewModel.isGenerating {
proxy.scrollTo("bottom", anchor: .bottom)
}
}
}
// Input bar
InputBar(
text: $viewModel.inputText,
commandHistory: $viewModel.commandHistory,
historyIndex: $viewModel.historyIndex,
isGenerating: viewModel.isGenerating,
mcpStatus: viewModel.mcpStatus,
onlineMode: viewModel.onlineMode,
onSend: viewModel.sendMessage,
onCancel: viewModel.cancelGeneration
)
// Footer
FooterView(stats: viewModel.sessionStats)
}
.background(Color.oaiBackground)
}
}
#Preview {
ChatView(onModelSelect: {}, onProviderChange: { _ in })
.environment(ChatViewModel())
}