Skip to content
Snippets Groups Projects
WorkflowsList.vue 9.83 KiB
Newer Older
  • Learn to ignore specific revisions
  • paulpestov's avatar
    paulpestov committed
    <template>
    
      <div>
        <div class="_display:flex _margin-bottom:4">
          <div class="_display:flex _align-items:center _margin-left:auto">
            <p class="_margin-right:2">{{ $t('sort_by')}}:</p>
            <i-select
    
                v-model="sortBy"
                :options="sortOptions"
    
                label="label"
                idField="value"
                placeholder="Choose something.."
                @update:modelValue="onChange($event.value)"
    
        <i-card v-for="(item, i) in list" :key="i" class="_margin-bottom:5 shadow-3 border-gray-200">
    
    paulpestov's avatar
    paulpestov committed
          <template #header>
    
            <div class="_display:flex _align-items:center">
              <h3 class="_font-size:lg _font-weight:bold">{{ item.label }}</h3>
              <i-badge class="bg-gray-300 text-gray-700 _margin-left:2">Model: {{ item.metadata.workflow_model }}</i-badge>
    
              <template v-if="item.metadata.document_metadata">
    
                  v-for="font in item.metadata.document_metadata.data_properties.fonts"
    
                  class="_margin-left:1 bg-gray-300 text-gray-700">
                  {{font}}
                </i-badge>
    
    paulpestov's avatar
    paulpestov committed
          </template>
    
          <template #default>
            <i-row>
              <i-column xs="7">
                <i-row class="_margin-top:2">
                  <i-column>
                    <template v-if="item.metadata.gt_workspace">
                      <i-collapsible class="_font-size:sm">
                        <i-collapsible-item :title="item.metadata.gt_workspace.label">
    
                          <i-row v-if="item.metadata.document_metadata.data_properties">
    
                            <i-column>
                              <p class="_font-weight:bold">{{ $t('number_of_pages') }}:</p>
                              <p>{{ item.metadata.document_metadata.data_properties.number_of_pages }}</p>
                              <p class="mt-2 _font-weight:bold">{{ $t('publication_year') }}:</p>
                              <p> {{ item.metadata.document_metadata.data_properties.publication_year }}</p>
                            </i-column>
                            <i-column>
                              <p class="_font-weight:bold">{{ $t('layout') }}:</p>
                              <p>{{ item.metadata.document_metadata.data_properties.layout }}</p>
                            </i-column>
                          </i-row>
    
                          <i-row v-else><i-column>{{ $t('no_document_metadata')}}</i-column></i-row>
    
                        </i-collapsible-item>
                      </i-collapsible>
                    </template>
                    <template v-else>
                      <p class="_font-weight:bold text-gray-400 mt-3">{{$t('no_gt_workspace')}}</p>
                    </template>
                  </i-column>
                  <i-column class="_display:flex-1">
                    <i-collapsible class="_font-size:sm">
                      <i-collapsible-item :title="item.metadata.ocr_workflow?.label || $t('unknown_workflow')">
                        <div class="_display:flex _flex-direction:column" v-if="item.metadata.workflow_steps">
                          <span class="_margin-bottom:1">{{ $t('workflow_steps')}}:</span>
    
                          <span v-for="({id, url}, i) in item.metadata.workflow_steps" :key="id">
    
                            <span>{{ i + 1 }}. </span>
    
                            <i-badge size="lg" class="_font-size:sm _margin-bottom:1/2">
                              <a v-if="url" :href="url" target="_blank" :title="$t('external_repo_url')" class="_display:flex _align-items:flex-end">
                                {{ id }}
                                <i class="repo-icon _margin-left:1/3" v-html="getIcon('external-link')"></i>
                              </a>
                              <template v-else>{{ id }}</template>
                            </i-badge>
    
                        <template v-else>
                          <span class="_font-weight:bold">{{$t('no_ocr_workflow')}}</span>
                        </template>
                      </i-collapsible-item>
                    </i-collapsible>
                  </i-column>
                </i-row>
              </i-column>
              <i-column xs="5" class="_margin-left:auto">
                <i-row>
                  <i-column class="_display:flex _justify-content:center" v-for="(evalKey, i) in evals" :key="i">
                    <span class="_font-weight:bold _font-size:xs">{{defs[evalKey] ? defs[evalKey].label : evalKey}}</span>
                  </i-column>
                </i-row>
                <i-row>
                  <i-column v-for="({ name, value }, i) in item.evaluations" :key="i" class="_text-align:center">
                  <i-badge
                      size="lg"
                      class="metric _cursor:pointer _padding-x:1"
    
                      :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>
    
                  </i-badge>
                  </i-column>
                </i-row>
              </i-column>
            </i-row>
    
    paulpestov's avatar
    paulpestov committed
          </template>
    
    paulpestov's avatar
    paulpestov committed
      </div>
    </template>
    
    <script setup>
    import { ref, onMounted, watch } from "vue";
    import { getEvalColor } from "@/helpers/eval-colors";
    
    import { useI18n } from "vue-i18n";
    
    import { getIcon } from "@/helpers/icon";
    import { store } from "@/helpers/store";
    
    const props = defineProps(['data', 'defs']);
    
    paulpestov's avatar
    paulpestov committed
    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
    }) => {
    
      workflow_steps = workflow_steps.map(step => ({ id: step, url: getRepoUrl(step) }));
    
      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]
      }));
    };
    
    paulpestov's avatar
    paulpestov committed
    
    const setListData = (data) => {
    
      list.value = data.map(({ label, evaluation_results = [], metadata }) => ({
    
    paulpestov's avatar
    paulpestov committed
        label,
    
        metadata: mapMetadata(metadata),
    
        evaluations: mapEvaluationResults(evaluation_results)
    
    const getRepoUrl = (needleId) => {
      const repo = store.repos.find(({ ocrd_tool }) => {
        return ocrd_tool && ocrd_tool.tools[needleId];
      });
      if (!repo) return null;
      return repo.url;
    };
    
    
    paulpestov's avatar
    paulpestov committed
    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);
    });
    
    paulpestov's avatar
    paulpestov committed
    
    watch(() => props.data, () => {
      setEvals(props.data);
      setListData(props.data);
    });
    
    </script>
    
    <style scoped lang="scss">
    
    
    .metric {
      --border-radius: 8px;
    }
    
    .card {
      ----header--padding-left: 1rem;
      ----header--padding-right: 1rem;
      ----body--padding-top: 1rem;
      ----body--padding-bottom: 1rem;
      ----body--padding-left: 1rem;
      ----body--padding-right: 1rem;
    }
    
    
    .arrow-icon, .repo-icon {
    
      width: 16px;
      height: 16px;
    
      :deep(svg) {
        width: 100%;
        height: 100%;
    
    paulpestov's avatar
    paulpestov committed
      }
    }
    </style>