docling-studio/frontend/src/features/chunks/ui/StaleStoresStrip.vue
Pier-Jean Malandrino 63dfcaab67 feat(#225,#224): vocabulary rename index→ingest + stale stores strip
- #225: rename all push/index/pousser/indexé labels to ingest/ingérer/ingéré
  across status tooltips, docs/chunks push modals, and legacy ingestion panel
- #224: add DocStoreLink type + storeLinks on Document; StaleStoresStrip
  component shows per-store state badges with one-click re-ingest buttons
2026-04-30 10:42:10 +02:00

143 lines
3.4 KiB
Vue

<template>
<div v-if="storeLinks.length > 0" class="stale-strip" data-e2e="stale-strip">
<div class="stale-strip__stores">
<div v-for="link in storeLinks" :key="link.store" class="store-row">
<span class="store-name">{{ link.store }}</span>
<StatusBadge :state="link.state" compact />
<span v-if="link.pushedAt" class="store-date">
{{ t('chunks.stale.pushedAt', { date: formatRelativeTime(link.pushedAt, locale) }) }}
</span>
<button
v-if="link.state === 'Stale'"
class="btn-reingest"
:disabled="reingestingStores.has(link.store)"
@click="reingest(link.store)"
>
{{ t('chunks.stale.reingest') }}
</button>
</div>
</div>
<button
v-if="staleCount > 1"
class="btn-reingest-all"
:disabled="reingestingStores.size > 0"
@click="reingestAll"
>
{{ t('chunks.stale.reingestAll') }}
</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { DocStoreLink } from '../../../shared/types'
import { pushChunksToStore } from '../api'
import StatusBadge from '../../document/ui/StatusBadge.vue'
import { useI18n } from '../../../shared/i18n'
import { formatRelativeTime } from '../../../shared/format'
import { appLocale } from '../../../shared/appConfig'
const props = defineProps<{
docId: string
storeLinks: DocStoreLink[]
}>()
const { t } = useI18n()
const locale = appLocale
const reingestingStores = ref<Set<string>>(new Set())
const staleCount = computed(() => props.storeLinks.filter((l) => l.state === 'Stale').length)
async function reingest(store: string): Promise<void> {
const next = new Set(reingestingStores.value)
next.add(store)
reingestingStores.value = next
try {
await pushChunksToStore(props.docId, store)
} catch {
// silent — user can retry
} finally {
const after = new Set(reingestingStores.value)
after.delete(store)
reingestingStores.value = after
}
}
async function reingestAll(): Promise<void> {
const stale = props.storeLinks.filter((l) => l.state === 'Stale').map((l) => l.store)
await Promise.all(stale.map(reingest))
}
</script>
<style scoped>
.stale-strip {
display: flex;
align-items: center;
gap: 12px;
padding: 6px 16px;
background: var(--bg-surface);
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
flex-shrink: 0;
}
.stale-strip__stores {
display: flex;
flex-wrap: wrap;
gap: 8px;
flex: 1;
}
.store-row {
display: flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: 12px;
}
.store-name {
font-weight: 500;
color: var(--text-secondary);
}
.store-date {
color: var(--text-muted);
font-size: 11px;
}
.btn-reingest,
.btn-reingest-all {
padding: 2px 8px;
font-size: 11px;
font-weight: 500;
background: var(--warning-bg, rgba(234, 179, 8, 0.1));
color: var(--warning, #ca8a04);
border: 1px solid var(--warning, #ca8a04);
border-radius: var(--radius-sm);
cursor: pointer;
transition: opacity var(--transition);
white-space: nowrap;
}
.btn-reingest:disabled,
.btn-reingest-all:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-reingest:hover:not(:disabled),
.btn-reingest-all:hover:not(:disabled) {
opacity: 0.8;
}
.btn-reingest-all {
flex-shrink: 0;
padding: 4px 10px;
font-size: 12px;
}
</style>