// // 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()) }