Newer
Older
navi-1 / webclient / src / components / chat / ChatHeader.vue
<template>
  <div class="chat-header">
    <GnIconButton icon="ph ph-sidebar" label="Menu" class="btn-sidebar-toggle" @click="emit('toggle-sidebar')" />

    <div class="chat-header-info">
      <span class="chat-header-title">{{ title }}</span>
      <ProfileBadge :profile-id="chatStore.currentProfileId" :show-avatar="false" />
    </div>

    <ContextBar />

    <GnIconButton icon="ph ph-files" label="Artifacts" class="btn-artifacts-toggle" @click="emit('toggle-artifacts')">
      <span v-if="artifactCount" class="artifact-count">{{ artifactCount }}</span>
    </GnIconButton>
  </div>

  <div v-if="chatStore.recall" class="recall-banner">
    <i class="ph ph-clock"></i>
    <span class="recall-text">
      Scheduled {{ chatStore.recall.call_type }} recall at {{ formatTime(chatStore.recall.trigger_at) }}
    </span>
    <button class="recall-btn" title="Cancel" @click="cancelRecall">
      <i class="ph ph-clock-slash"></i>
    </button>
    <button v-if="chatStore.recall.call_type === 'recurring'" class="recall-btn" title="Skip next" @click="skipRecall">
      <i class="ph ph-skip-forward"></i>
    </button>
  </div>
</template>

<script setup>
import { computed, watch } from 'vue'
import { useChatStore } from '@/stores/chat.js'
import { useSessionsStore } from '@/stores/sessions.js'
import ProfileBadge from '@/components/ui/ProfileBadge.vue'
import ContextBar from '@/components/ui/ContextBar.vue'
import * as api from '@/api/index.js'

const emit = defineEmits(['toggle-sidebar', 'toggle-artifacts'])

const chatStore = useChatStore()
const sessionsStore = useSessionsStore()

const title = computed(() => {
  const session = sessionsStore.sessions.find(s => s.session_id === chatStore.currentId)
  return session?.name || (chatStore.currentId?.slice(0, 8) ?? 'Navi')
})

const artifactCount = computed(() => chatStore.artifacts.length)

function formatTime(iso) {
  if (!iso) return ''
  const d = new Date(iso)
  return d.toLocaleString()
}

watch(() => chatStore.currentId, chatStore.loadRecall, { immediate: true })

async function cancelRecall() {
  if (!chatStore.currentId) return
  try {
    await api.cancelSessionRecall(chatStore.currentId)
    chatStore.recall = null
    sessionsStore.updateRecallStatus(chatStore.currentId, false)
  } catch (e) {
    console.error('cancel recall failed', e)
  }
}

async function skipRecall() {
  if (!chatStore.currentId) return
  try {
    await api.skipSessionRecall(chatStore.currentId)
    await chatStore.loadRecall()
  } catch (e) {
    console.error('skip recall failed', e)
  }
}
</script>

<style scoped>
.btn-artifacts-toggle {
  position: relative;
}

.artifact-count {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
  font-size: 10px;
  line-height: 1;
  background: var(--accent, #4ec9b0);
  color: #11111b;
}

.recall-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  background: rgba(var(--color-accent-rgb, 122, 162, 247), 0.12);
  border-bottom: 1px solid var(--color-border, #3b4261);
  font-size: 12px;
  color: var(--color-text, #a9b1d6);
}

.recall-banner i {
  color: var(--color-accent, #7aa2f7);
  font-size: 14px;
}

.recall-text {
  flex: 1;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.recall-btn {
  background: none;
  border: none;
  color: var(--color-text-dark, #787c99);
  cursor: pointer;
  padding: 2px 4px;
  font-size: 14px;
  line-height: 1;
  border-radius: 4px;
  transition: color 0.15s, background 0.15s;
}

.recall-btn:hover {
  color: var(--color-text, #a9b1d6);
  background: rgba(255,255,255,0.06);
}
</style>