kevinhermawan / Ollamac

Mac app for Ollama
Other
991 stars 52 forks source link

Scrolling while generation is in progress #14

Open MaxKlyukin opened 7 months ago

MaxKlyukin commented 7 months ago

The problem: It's hard to read a response while the app automatically scrolls to the last line in the response.

Can you please look into making user scrolling possible while generation is in progress?

kevinhermawan commented 7 months ago

Hi @MaxKlyukin, thanks for pointing that out. I totally missed the scrolling issue. I'm on it - will check it out and see what I can do to make the scrolling more user-friendly.

itopaloglu83 commented 6 months ago

+1

It would be great if the autoscroll can be suspended when the user selects a text or scrolls manually. The autoscroll later can be continued when a new message is sent by the user.

https://github.com/kevinhermawan/Ollamac/blob/d5d17bbab8fff25af5f040656e9e940b8203952c/Ollamac/Views/MessageViews/MessageView.swift#L139

I think I was able to get something that might work from ChatGPT for a messaging app. The idea is to keep track of if the user scrolled to the bottom manually and only then do the autoscroll.

import SwiftUI

struct Message: Identifiable {
    var id = UUID()
    var text: String
}

struct ChatView: View {
    @State private var messages: [Message] = [
        Message(text: "Hello"),
        Message(text: "How are you?"),
        Message(text: "Nice to meet you!"),
    ]

    @State private var isScrolledToBottom = true

    var body: some View {
        VStack {
            ScrollViewReader { scrollViewProxy in
                ScrollView {
                    VStack(spacing: 10) {
                        ForEach(messages) { message in
                            Text(message.text)
                                .padding(8)
                                .background(Color.blue)
                                .foregroundColor(.white)
                                .clipShape(RoundedRectangle(cornerRadius: 10))
                                .id(message.id)
                        }
                    }
                    .padding()
                    .onChange(of: messages) { _ in
                        // Automatically scroll to the bottom only if the user is already at the bottom
                        if isScrolledToBottom {
                            withAnimation {
                                scrollViewProxy.scrollTo(messages.last?.id, anchor: .bottom)
                            }
                        }
                    }
                    .onAppear {
                        // Set initial scroll position to the bottom
                        scrollViewProxy.scrollTo(messages.last?.id, anchor: .bottom)
                    }
                    .onTapGesture {
                        // User manually scrolled, so disable automatic scrolling
                        isScrolledToBottom = false
                    }
                    .onEnded { _ in
                        // User finished scrolling, check if at the bottom
                        let currentOffset = scrollViewProxy.contentOffset.y
                        let maxY = scrollViewProxy.frame(in: .local).maxY
                        isScrolledToBottom = currentOffset > maxY - 10 // Consider a small threshold
                    }
                }
            }

            // Example: Add a new message
            Button("Add Message") {
                let newMessage = Message(text: "New message")
                messages.append(newMessage)
            }
            .padding()
        }
        .navigationTitle("Chat")
    }
}

struct ChatView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            ChatView()
        }
    }
}