Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/components/map-projects/MapProject.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2216,6 +2216,24 @@ const MapProject = () => {

const getAllCandidatesForRow = index => flatten(map(allCandidatesRef.current, candidates => getCandidatesForRow(index, candidates)))

const getRawScoresForConcept = (index, concept) => {
if(!concept || !isNumber(index))
return []

return compact(map(allCandidates, (candidates, algorithm) => {
const rowCandidates = getCandidatesForRow(index, candidates)
const matchingConcept = find(
rowCandidates,
candidate => candidate?.url === concept?.url || (
candidate?.id === concept?.id &&
(candidate?.source || candidate?.repo?.id || candidate?.repo?.short_code) === (concept?.source || concept?.repo?.id || concept?.repo?.short_code)
)
)
const score = parseFloat(matchingConcept?.search_meta?.search_score)
return Number.isFinite(score) ? {algorithm, score: score.toFixed(2)} : null
}))
}

const isReadyForRerank = _index => {
const index = isNumber(_index) ? _index : rowIndex
if(isNumber(index) && get(rowStageRef.current, `${index}.rerank`) !== 0) {
Expand Down Expand Up @@ -2607,7 +2625,7 @@ const MapProject = () => {
return permissionDenied ? <Error403/> : (
<div className='col-xs-12 padding-0' style={{borderRadius: '10px', width: 'calc(100vw - 32px)'}}>
{
Boolean(repoVersion?.url) && CIELMappedSources.length &&
Boolean(repoVersion?.url) && CIELMappedSources.length > 0 &&
<BridgeMatch
service={getMatchAPIService()}
repo={repoVersion}
Expand Down Expand Up @@ -3148,9 +3166,9 @@ const MapProject = () => {
<SearchHighlightsDialog
open={Boolean(showHighlights)}
onClose={() => setShowHighlights(false)}
highlight={showHighlights?.search_meta?.search_highlight || []}
score={parseFloat(showHighlights?.search_meta?.search_normalized_score || 0).toFixed(2)}
raw_score={parseFloat(showHighlights?.search_meta?.search_score || 0).toFixed(2)}
concept={showHighlights}
rawScores={getRawScoresForConcept(rowIndex, showHighlights)}
candidatesScore={candidatesScore}
/>
</> :
<ProjectLogs open={showProjectLogs} onClose={() => setShowProjectLogs(false) } logs={projectLogs} project={project} />
Expand Down
65 changes: 47 additions & 18 deletions src/components/map-projects/Score.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@ import isNaN from 'lodash/isNaN'
import ConceptIcon from '../concepts/ConceptIcon'



const Score = ({concept, setShowHighlights, sx, isAIRecommended, candidatesScore, algoScoreFirst, size}) => {
const { t } = useTranslation();
export const getScoreDetails = (concept, candidatesScore) => {
let percentile = concept?.search_meta?.search_normalized_score || ((concept?.search_meta?.search_rerank_score || concept?.search_meta?.search_score) * 100)
if(percentile && !isNumber(percentile))
percentile = parseFloat(percentile)

const score = concept?.search_meta?.search_score
const hasPercentile = isNumber(percentile)
const { color } = MATCH_TYPES[concept?.search_meta?.match_type || 'no_match']
const recommendedScore = candidatesScore?.recommended
const availableScore = candidatesScore?.available

let qualityBucket;
if(hasPercentile) {
if (percentile >= recommendedScore)
Expand All @@ -35,10 +34,49 @@ const Score = ({concept, setShowHighlights, sx, isAIRecommended, candidatesScore
else
qualityBucket = 'low_ranked'
}
let bucketColor = qualityBucket ? SCORES_COLOR[qualityBucket] : false

const rerankScore = `${parseFloat(hasPercentile ? percentile : score).toFixed(2)}%`
const algoScore = `${parseFloat(score).toFixed(2)}`
return {
score,
percentile,
hasPercentile,
qualityBucket,
bucketColor: qualityBucket ? SCORES_COLOR[qualityBucket] : false,
rerankScore: `${parseFloat(hasPercentile ? percentile : score).toFixed(2)}%`,
algoScore: `${parseFloat(score).toFixed(2)}`
}
}

export const ScoreValueChip = ({ bucketColor, label, size='medium', showIndicator=true, sx }) => (
<Chip
size={size}
icon={
showIndicator ? (
<Box sx={{
border: '1px solid',
borderColor: '#FFF',
borderRadius: '50%',
display: 'inline-flex',
}}>
<ConceptIcon fontSize='small' selected sx={{fill: bucketColor || 'rgba(0, 0, 0, 0.5)', fontSize: '1rem'}} />
</Box>
) : undefined
}
label={label}
sx={sx}
/>
)

const Score = ({concept, setShowHighlights, sx, isAIRecommended, candidatesScore, algoScoreFirst, size}) => {
const { t } = useTranslation();
const {
score,
hasPercentile,
bucketColor,
rerankScore,
algoScore
} = getScoreDetails(concept, candidatesScore)
const { color } = MATCH_TYPES[concept?.search_meta?.match_type || 'no_match']

return (
<ListItem disablePadding sx={{display: 'inline-flex', width: 'auto'}}>
<ListItemButton
Expand All @@ -64,18 +102,9 @@ const Score = ({concept, setShowHighlights, sx, isAIRecommended, candidatesScore
<ListItemText
sx={{margin: 0, '.MuiListItemText-primary': {fontSize: '14px'}, '.MuiListItemText-secondary': {fontSize: '12px'}}}
primary={
<Chip
<ScoreValueChip
size={size || 'medium'}
icon={
<Box sx={{
border: '1px solid',
borderColor: '#FFF',
borderRadius: '50%',
display: 'inline-flex',
}}>
<ConceptIcon fontSize='small' selected sx={{fill: bucketColor || 'rgba(0, 0, 0, 0.5)', fontSize: '1rem'}} />
</Box>
}
bucketColor={bucketColor}
label={
<span style={{display: 'flex', alignItems: 'center'}}>
<span>{algoScoreFirst ? algoScore : rerankScore}</span>
Expand Down
220 changes: 140 additions & 80 deletions src/components/search/SearchHighlightsDialog.jsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import React from 'react';
import { useTranslation } from 'react-i18next'

import Box from '@mui/material/Box'
import Chip from '@mui/material/Chip'
import Dialog from '@mui/material/Dialog'
import DialogTitle from '@mui/material/DialogTitle'
import DialogContent from '@mui/material/DialogContent'
import DialogActions from '@mui/material/DialogActions'
import Divider from '@mui/material/Divider'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import List from '@mui/material/List'
import ListItem from '@mui/material/ListItem'
import ListItemText from '@mui/material/ListItemText'

import startCase from 'lodash/startCase'
import isArray from 'lodash/isArray'
import map from 'lodash/map'
import startCase from 'lodash/startCase'

import Link from '../common/Link'

import { getScoreDetails, ScoreValueChip } from '../map-projects/Score'
import CloseIconButton from '../common/CloseIconButton'

const SearchHighlightsDialog = ({onClose, highlight, score, raw_score, open}) => {
const SearchHighlightsDialog = ({onClose, concept, rawScores, candidatesScore, open}) => {
const { t } = useTranslation()
const highlight = concept?.search_meta?.search_highlight || {}
const { hasPercentile, bucketColor, rerankScore } = getScoreDetails(concept, candidatesScore)

return (
<Dialog
open={Boolean(open)}
onClose={onClose}
scroll='paper'
fullWidth
maxWidth='sm'
sx={{
'& .MuiDialog-paper': {
backgroundColor: 'surface.n92',
Expand All @@ -33,81 +38,136 @@ const SearchHighlightsDialog = ({onClose, highlight, score, raw_score, open}) =>
}
}}
>
<DialogTitle sx={{p: 3, color: 'surface.dark', fontSize: '22px', textAlign: 'left'}}>
{t('search.search_highlight')}
</DialogTitle>
<DialogContent style={{padding: 0}}>
<List dense sx={{ width: '100%', bgcolor: 'surface.n92', padding: '0 10px', maxHeight: 700 }}>
{
map(highlight, (values, key) => (
<React.Fragment key={key}>
<ListItem>
<ListItemText
<DialogContent sx={{padding: 3, maxHeight: 700}}>
<Stack spacing={2.5}>
<Stack direction='row' justifyContent='space-between' alignItems='center' spacing={2}>
<Typography sx={{color: 'surface.dark', fontSize: '22px', lineHeight: 1.2}}>
{t('search.search_highlight')}
</Typography>
<CloseIconButton size='small' onClick={onClose} sx={{alignSelf: 'flex-start'}} />
</Stack>

<Stack
spacing={1.25}
sx={{
alignSelf: 'flex-start',
minWidth: { xs: '100%', sm: 'auto' },
maxWidth: '100%',
padding: '12px 14px',
borderRadius: '16px',
backgroundColor: 'rgba(255, 255, 255, 0.4)',
border: '1px solid rgba(0, 0, 0, 0.08)'
}}
>
{
hasPercentile &&
<Stack
direction='row'
spacing={1.5}
alignItems='center'
justifyContent='space-between'
sx={{ minWidth: { sm: '260px' } }}
>
<Typography sx={{fontSize: '12px', color: 'surface.light', fontWeight: 600, whiteSpace: 'nowrap'}}>
{t('search.search_score')}
</Typography>
<ScoreValueChip size='small' bucketColor={bucketColor} label={rerankScore} />
</Stack>
}

<Stack spacing={0.75}>
<Stack
direction='row'
spacing={1.5}
alignItems='flex-start'
justifyContent='space-between'
sx={{ minWidth: { sm: '260px' } }}
>
<Typography sx={{fontSize: '12px', color: 'surface.light', fontWeight: 600, whiteSpace: 'nowrap', paddingTop: '4px'}}>
{t('search.search_raw_score')}
</Typography>
{
rawScores?.length ? (
<Stack spacing={0.75} alignItems='flex-end'>
{map(rawScores, (rawScore, index) => (
<Stack
key={`${rawScore.algorithm}-${rawScore.score}-${index}`}
direction='row'
spacing={0.75}
alignItems='center'
sx={{
flexWrap: 'nowrap',
width: 'fit-content'
}}
>
<Chip size='small' label={rawScore.algorithm} variant='outlined' color='warning' />
<ScoreValueChip
size='small'
showIndicator={false}
label={rawScore.score}
sx={{
backgroundColor: 'rgba(0, 0, 0, 0.08)',
color: 'surface.dark',
fontWeight: 600
}}
/>
</Stack>
))}
</Stack>
) : (
<Typography sx={{fontSize: '13px', color: 'surface.light', textAlign: 'right', paddingTop: '4px'}}>
{t('common.none')}
</Typography>
)
}
</Stack>
</Stack>
</Stack>

<Divider />

<Stack spacing={1.5}>
<Typography sx={{fontSize: '16px', color: 'surface.dark', fontWeight: 700}}>
{t('search.matched_attributes')}
</Typography>
{
map(highlight, (values, key) => (
<Box key={key}>
<Typography sx={{fontSize: '12px', color: 'surface.light', fontWeight: 600, marginBottom: 0.75}}>
{startCase(key)}
</Typography>
<Stack
spacing={isArray(values) && key === 'synonyms' ? 1.25 : 0.75}
sx={{
'.MuiListItemText-primary': {color: 'surface.dark', fontSize: '12px'},
'.MuiListItemText-secondary': {color: 'default.light', fontSize: '14px'}
paddingLeft: 1,
'& b': {
color: 'surface.dark'
}
}}
primary={startCase(key)}
secondary={
<React.Fragment>
{
map(values, value => {
value = value.replaceAll('<em>', '<b>').replaceAll('</em>', '</b>')
return (
<Typography
key={value}
component="span"
sx={{ color: 'text.primary', display: 'inline-block' }}
dangerouslySetInnerHTML={{__html: value}}
/>
)})
}
</React.Fragment>
>
{
map(values, value => {
const highlightedValue = value.replaceAll('<em>', '<b>').replaceAll('</em>', '</b>')
return (
<Typography
key={value}
sx={{
color: 'text.primary',
fontSize: key === 'synonyms' ? '12px' : '13px',
lineHeight: key === 'synonyms' ? 0.95 : 1.3
}}
dangerouslySetInnerHTML={{__html: highlightedValue}}
/>
)
})
}
/>
</ListItem>
</React.Fragment>
))
}
<ListItem>
<ListItemText
sx={{
'.MuiListItemText-primary': {color: 'surface.dark', fontSize: '12px'},
'.MuiListItemText-secondary': {color: 'default.light', fontSize: '14px'}
}}
primary={t('search.search_score')}
secondary={
<Typography
component="span"
sx={{ color: 'text.primary', display: 'flex', fontWeight: 'bold' }}
>
{score}
</Typography>
}
/>
</ListItem>
<ListItem>
<ListItemText
sx={{
'.MuiListItemText-primary': {color: 'surface.dark', fontSize: '12px'},
'.MuiListItemText-secondary': {color: 'default.light', fontSize: '14px'}
}}
primary={t('search.search_raw_score')}
secondary={
<Typography
component="span"
sx={{ color: 'text.primary', display: 'flex', fontWeight: 'bold' }}
>
{raw_score}
</Typography>
}
/>
</ListItem>
</List>
</Stack>
</Box>
))
}
</Stack>
</Stack>
</DialogContent>
<DialogActions sx={{p: 3}}>
<Link sx={{fontSize: '14px'}} label={t('common.close')} onClick={onClose} />
</DialogActions>
</Dialog>
)
}
Expand Down
Loading