Newer
Older
navi-1 / webclient / src / components / chat / MessageList.vue
<template>
  <div ref="containerEl" class="message-list" @scroll="onScroll">
    <div class="message-list-inner">
      <TransitionGroup name="slide-up" tag="div" class="messages-group">
        <component
          :is="resolveComponent(msg)"
          v-for="msg in chat.messages"
          :key="msg.id"
          :msg="msg"
        />
      </TransitionGroup>
    </div>
  </div>
</template>

<script setup>
import { ref, watch, nextTick } from 'vue'
import { useChatStore } from '@/stores/chat.js'
import UserMessage from '@/components/messages/UserMessage.vue'
import AssistantMessage from '@/components/messages/AssistantMessage.vue'
import SummaryCard from '@/components/messages/SummaryCard.vue'
import CompressionNotice from '@/components/messages/CompressionNotice.vue'
import ErrorMessage from '@/components/messages/ErrorMessage.vue'

const chat = useChatStore()
const containerEl = ref(null)
let userScrolledUp = false

function resolveComponent(msg) {
  if (msg.role === 'user') return UserMessage
  if (msg.type === 'summary') return SummaryCard
  if (msg.type === 'compression_notice') return CompressionNotice
  if (msg.type === 'error') return ErrorMessage
  return AssistantMessage
}

function scrollToBottom(smooth = false) {
  const el = containerEl.value
  if (!el) return
  el.scrollTo({ top: el.scrollHeight, behavior: smooth ? 'smooth' : 'instant' })
}

function onScroll() {
  const el = containerEl.value
  if (!el) return
  const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight
  userScrolledUp = distFromBottom > 100
}

// Auto-scroll when messages change
watch(
  () => chat.messages.length,
  () => {
    if (!userScrolledUp) nextTick(() => scrollToBottom())
  }
)

// Auto-scroll during streaming text
watch(
  () => chat.streamingMsg?.text,
  () => {
    if (!userScrolledUp) nextTick(() => scrollToBottom())
  }
)

// Auto-scroll when tool cards are added or complete
watch(
  () => chat.streamingMsg?.tools?.length,
  () => {
    if (!userScrolledUp) nextTick(() => scrollToBottom())
  }
)

// When session changes, scroll to bottom immediately
watch(
  () => chat.currentId,
  () => {
    userScrolledUp = false
    nextTick(() => scrollToBottom())
  }
)
</script>

<style scoped>
.messages-group {
  display: flex;
  flex-direction: column;
  gap: 32px;
}
</style>