<template> <div class="container"> <div class="grid mb-4"> <div class="flex align-items-center ml-auto"> <p class="mr-2">{{ $t('sort_by')}}:</p> <Dropdown v-model="sortBy" :options="sortOptions" option-label="label" @change="onChange($event.value)" /> </div> </div> <Card v-for="(item, i) in list" :key="i" class="mb-3 shadow-3 border-gray-200 p-4"> <template #header> <div class="grid align-items-center gap-2"> <h3 class="text-xl font-bold">{{ item.label }}</h3> <Tag :value="item.metadata.workflow_model" class="bg-gray-300 text-gray-700 ml-3"></Tag> <template v-if="item.metadata.document_metadata"> <Tag v-for="font in item.metadata.document_metadata.data_properties.fonts" :key="font" :value="font" class="bg-gray-300 text-gray-700"> </Tag> </template> </div> </template> <template #content> <div class="grid"> <div class="col-7 grid"> <div class="mr-3"> <template v-if="item.metadata.gt_workspace"> <Accordion class="text-sm"> <AccordionTab :header="item.metadata.gt_workspace.label"> <div class="grid"> <div class="col"> <p class="font-bold">{{ $t('number_of_pages') }}:</p> <p>{{ item.metadata.document_metadata.data_properties.number_of_pages }}</p> <p class="mt-2 font-bold">{{ $t('publication_year') }}:</p> <p> {{ item.metadata.document_metadata.data_properties.publication_year }}</p> </div> <div class="col"> <p class="font-bold">{{ $t('layout') }}:</p> <p>{{ item.metadata.document_metadata.data_properties.layout }}</p> </div> </div> </AccordionTab> </Accordion> </template> <template v-else> <p class="font-bold text-gray-400 mt-3">{{$t('no_gt_workspace')}}</p> </template> </div> <div class="flex-1"> <Accordion class="text-sm"> <AccordionTab :header="item.metadata.ocr_workflow?.label || $t('unknown_workflow')"> <div class="flex gap-2" v-if="item.metadata.workflow_steps"> <div v-for="step in item.metadata.workflow_steps" :key="step"> <Chip :label="step" class="text-sm" /> </div> </div> <template v-else> <span class="font-bold text-gray-400">{{$t('no_ocr_workflow')}}</span> </template> </AccordionTab> </Accordion> </div> </div> <div class="col-5 py-0 ml-auto"> <div class="grid mb-1"> <div class="col py-0 text-center" v-for="(evalKey, i) in evals" :key="i"> <span class="font-bold">{{defs[evalKey] ? defs[evalKey].label : evalKey}}</span> </div> </div> <div class="grid"> <div v-for="({ name, value }, i) in item.evaluations" :key="i" class="col text-center"> <span class="border-round-3xl py-1 px-3 cursor-pointer" :class="getEvalColor(name, value)" :title="value"> <template v-if=" name === 'cer'">{{ shortenCER(value) }}</template> <template v-else-if="name === 'cer_min_max'">{{ shortenCER(value[0]) + '/' + shortenCER(value[1])}}</template> <template v-else>{{ value }}</template> </span> </div> </div> </div> </div> </template> </Card> </div> </template> <script setup> import { ref, onMounted, watch } from "vue"; import { getEvalColor } from "@/helpers/eval-colors"; import { useI18n } from "vue-i18n"; const props = defineProps(['data', 'defs']); const list = ref([]); const evals = ref([]); const { t } = useI18n(); const sortOptions = ref([ { value: 'none', label: t('-') }, { value: 'wall_time_asc', label: t('wall_time_asc') }, { value: 'wall_time_desc', label: t('wall_time_desc') }, { value: 'cer_asc', label: t('cer_asc') }, { value: 'cer_desc', label: t('cer_desc') }, { value: 'cer_min_asc', label: t('cer_min_asc') }, { value: 'cer_min_desc', label: t('cer_min_desc') }, { value: 'cer_max_asc', label: t('cer_max_asc') }, { value: 'cer_max_desc', label: t('cer_max_desc') } ]); const sortBy = ref(sortOptions.value[0]); const onChange = ({ value }) => { if (value === 'wall_time_asc') sortByWallTime('asc'); else if (value === 'wall_time_desc') sortByWallTime('desc'); else if (value === 'cer_asc') sortByCER('asc'); else if (value === 'cer_desc') sortByCER('desc'); else if (value === 'cer_min_asc') sortByCERMin('asc'); else if (value === 'cer_min_desc') sortByCERMin('desc'); else if (value === 'cer_max_asc') sortByCERMax('asc'); else if (value === 'cer_max_desc') sortByCERMax('desc'); }; const sortByWallTime = (order = 'asc') => { list.value.sort((a, b) => { const wallTimeA = a.evaluations.find(e => e.name === 'wall_time')?.value || 0; const wallTimeB = b.evaluations.find(e => e.name === 'wall_time')?.value || 0; if (order === 'asc') return wallTimeA > wallTimeB ? 1 : -1; if (order === 'desc') return wallTimeA < wallTimeB ? 1 : -1; return 0; }); }; const sortByCER = (order = 'asc') => { list.value.sort((a, b) => { const cerA = a.evaluations.find(e => e.name === 'cer')?.value || 0; const cerB = b.evaluations.find(e => e.name === 'cer')?.value || 0; if (order === 'asc') return cerA > cerB ? 1 : -1; if (order === 'desc') return cerA < cerB ? 1 : -1; return 0; }); }; const sortByCERMin = (order = 'asc') => { list.value.sort((a, b) => { const cerMinA = a.evaluations.find(e => e.name === 'cer_min_max')?.value[0] || 0; const cerMinB = b.evaluations.find(e => e.name === 'cer_min_max')?.value[0] || 0; if (order === 'asc') return cerMinA > cerMinB ? 1 : -1; if (order === 'desc') return cerMinA < cerMinB ? 1 : -1; return 0; }); }; const sortByCERMax = (order = 'asc') => { list.value.sort((a, b) => { const cerMaxA = a.evaluations.find(e => e.name === 'cer_min_max')?.value[1] || 0; const cerMaxB = b.evaluations.find(e => e.name === 'cer_min_max')?.value[1] || 0; if (order === 'asc') return cerMaxA > cerMaxB ? 1 : -1; if (order === 'desc') return cerMaxA < cerMaxB ? 1 : -1; return 0; }); }; const mapMetadata = ({ workflow_model = t('no_workflow_model'), document_metadata = { fonts: [] }, gt_workspace = null, ocr_workflow = null, workflow_steps = null }) => { return { workflow_model, document_metadata, gt_workspace, ocr_workflow, workflow_steps }; }; const mapEvaluationResults = ({ document_wide = [] }) => { return Object.keys(document_wide).map(key => ({ name: key, value: document_wide[key] })); }; const setListData = (data) => { list.value = data.map(({ label, evaluation_results = [], metadata }) => ({ label, metadata: mapMetadata(metadata), evaluations: mapEvaluationResults(evaluation_results) })); }; const setEvals = (data) => { evals.value = data && data.length > 0 && data[0].evaluation_results ? Object.keys(data[0].evaluation_results.document_wide) || [] : []; }; const shortenCER = (value) => { return Math.round(value * 1000) / 1000; }; onMounted(() => { setEvals(props.data); setListData(props.data); }); watch(() => props.data, () => { setEvals(props.data); setListData(props.data); }); </script> <style scoped lang="scss"> @import 'primeflex/primeflex.scss'; .p-card { :deep(.p-card-content) { padding: 0; } :deep(.p-card-body) { @include styleclass('p-0 pt-5') } } </style>