<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="recall" class="recall-banner">
<i class="ph ph-clock"></i>
<span class="recall-text">
Scheduled {{ recall.call_type }} recall at {{ formatTime(recall.trigger_at) }}
</span>
<button class="recall-btn" title="Cancel" @click="cancelRecall">
<i class="ph ph-clock-slash"></i>
</button>
<button v-if="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, ref, watch } from 'vue'
import { useChatStore } from '@/stores/chat.js'
import { useSessionsStore } from '@/stores/sessions.js'
import * as api from '@/api/index.js'
import ProfileBadge from '@/components/ui/ProfileBadge.vue'
import ContextBar from '@/components/ui/ContextBar.vue'
const emit = defineEmits(['toggle-sidebar', 'toggle-artifacts'])
const chatStore = useChatStore()
const sessionsStore = useSessionsStore()
const recall = ref(null)
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()
}
async function loadRecall() {
if (!chatStore.currentId) {
recall.value = null
return
}
try {
const data = await api.getSessionRecall(chatStore.currentId)
if (data && data.id) {
recall.value = data
} else {
recall.value = null
}
} catch {
recall.value = null
}
}
watch(() => chatStore.currentId, loadRecall, { immediate: true })
async function cancelRecall() {
if (!chatStore.currentId) return
try {
await api.cancelSessionRecall(chatStore.currentId)
recall.value = 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 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>