Skip to content

Commit 2dc637d

Browse files
authored
Merge pull request #7 from OpenConceptLab/issues#2315
OpenConceptLab/ocl_issues#2315 | Updated Search Highlights dialog
2 parents 7975f49 + 6cd8284 commit 2dc637d

6 files changed

Lines changed: 220 additions & 110 deletions

File tree

src/components/map-projects/MapProject.jsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2228,6 +2228,24 @@ const MapProject = () => {
22282228

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

2231+
const getRawScoresForConcept = (index, concept) => {
2232+
if(!concept || !isNumber(index))
2233+
return []
2234+
2235+
return compact(map(allCandidates, (candidates, algorithm) => {
2236+
const rowCandidates = getCandidatesForRow(index, candidates)
2237+
const matchingConcept = find(
2238+
rowCandidates,
2239+
candidate => candidate?.url === concept?.url || (
2240+
candidate?.id === concept?.id &&
2241+
(candidate?.source || candidate?.repo?.id || candidate?.repo?.short_code) === (concept?.source || concept?.repo?.id || concept?.repo?.short_code)
2242+
)
2243+
)
2244+
const score = parseFloat(matchingConcept?.search_meta?.search_score)
2245+
return Number.isFinite(score) ? {algorithm, score: score.toFixed(2)} : null
2246+
}))
2247+
}
2248+
22312249
const isReadyForRerank = _index => {
22322250
const index = isNumber(_index) ? _index : rowIndex
22332251
if(isNumber(index) && get(rowStageRef.current, `${index}.rerank`) !== 0) {
@@ -3160,9 +3178,9 @@ const MapProject = () => {
31603178
<SearchHighlightsDialog
31613179
open={Boolean(showHighlights)}
31623180
onClose={() => setShowHighlights(false)}
3163-
highlight={showHighlights?.search_meta?.search_highlight || []}
3164-
score={parseFloat(showHighlights?.search_meta?.search_normalized_score || 0).toFixed(2)}
3165-
raw_score={parseFloat(showHighlights?.search_meta?.search_score || 0).toFixed(2)}
3181+
concept={showHighlights}
3182+
rawScores={getRawScoresForConcept(rowIndex, showHighlights)}
3183+
candidatesScore={candidatesScore}
31663184
/>
31673185
</> :
31683186
<ProjectLogs open={showProjectLogs} onClose={() => setShowProjectLogs(false) } logs={projectLogs} project={project} />

src/components/map-projects/Score.jsx

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,16 @@ import isNaN from 'lodash/isNaN'
1515
import ConceptIcon from '../concepts/ConceptIcon'
1616

1717

18-
19-
const Score = ({concept, setShowHighlights, sx, isAIRecommended, candidatesScore, algoScoreFirst, size}) => {
20-
const { t } = useTranslation();
18+
export const getScoreDetails = (concept, candidatesScore) => {
2119
let percentile = concept?.search_meta?.search_normalized_score || ((concept?.search_meta?.search_rerank_score || concept?.search_meta?.search_score) * 100)
2220
if(percentile && !isNumber(percentile))
2321
percentile = parseFloat(percentile)
22+
2423
const score = concept?.search_meta?.search_score
2524
const hasPercentile = isNumber(percentile)
26-
const { color } = MATCH_TYPES[concept?.search_meta?.match_type || 'no_match']
2725
const recommendedScore = candidatesScore?.recommended
2826
const availableScore = candidatesScore?.available
27+
2928
let qualityBucket;
3029
if(hasPercentile) {
3130
if (percentile >= recommendedScore)
@@ -35,10 +34,49 @@ const Score = ({concept, setShowHighlights, sx, isAIRecommended, candidatesScore
3534
else
3635
qualityBucket = 'low_ranked'
3736
}
38-
let bucketColor = qualityBucket ? SCORES_COLOR[qualityBucket] : false
3937

40-
const rerankScore = `${parseFloat(hasPercentile ? percentile : score).toFixed(2)}%`
41-
const algoScore = `${parseFloat(score).toFixed(2)}`
38+
return {
39+
score,
40+
percentile,
41+
hasPercentile,
42+
qualityBucket,
43+
bucketColor: qualityBucket ? SCORES_COLOR[qualityBucket] : false,
44+
rerankScore: `${parseFloat(hasPercentile ? percentile : score).toFixed(2)}%`,
45+
algoScore: `${parseFloat(score).toFixed(2)}`
46+
}
47+
}
48+
49+
export const ScoreValueChip = ({ bucketColor, label, size='medium', showIndicator=true, sx }) => (
50+
<Chip
51+
size={size}
52+
icon={
53+
showIndicator ? (
54+
<Box sx={{
55+
border: '1px solid',
56+
borderColor: '#FFF',
57+
borderRadius: '50%',
58+
display: 'inline-flex',
59+
}}>
60+
<ConceptIcon fontSize='small' selected sx={{fill: bucketColor || 'rgba(0, 0, 0, 0.5)', fontSize: '1rem'}} />
61+
</Box>
62+
) : undefined
63+
}
64+
label={label}
65+
sx={sx}
66+
/>
67+
)
68+
69+
const Score = ({concept, setShowHighlights, sx, isAIRecommended, candidatesScore, algoScoreFirst, size}) => {
70+
const { t } = useTranslation();
71+
const {
72+
score,
73+
hasPercentile,
74+
bucketColor,
75+
rerankScore,
76+
algoScore
77+
} = getScoreDetails(concept, candidatesScore)
78+
const { color } = MATCH_TYPES[concept?.search_meta?.match_type || 'no_match']
79+
4280
return (
4381
<ListItem disablePadding sx={{display: 'inline-flex', width: 'auto'}}>
4482
<ListItemButton
@@ -64,18 +102,9 @@ const Score = ({concept, setShowHighlights, sx, isAIRecommended, candidatesScore
64102
<ListItemText
65103
sx={{margin: 0, '.MuiListItemText-primary': {fontSize: '14px'}, '.MuiListItemText-secondary': {fontSize: '12px'}}}
66104
primary={
67-
<Chip
105+
<ScoreValueChip
68106
size={size || 'medium'}
69-
icon={
70-
<Box sx={{
71-
border: '1px solid',
72-
borderColor: '#FFF',
73-
borderRadius: '50%',
74-
display: 'inline-flex',
75-
}}>
76-
<ConceptIcon fontSize='small' selected sx={{fill: bucketColor || 'rgba(0, 0, 0, 0.5)', fontSize: '1rem'}} />
77-
</Box>
78-
}
107+
bucketColor={bucketColor}
79108
label={
80109
<span style={{display: 'flex', alignItems: 'center'}}>
81110
<span>{algoScoreFirst ? algoScore : rerankScore}</span>

src/components/search/SearchHighlightsDialog.jsx

Lines changed: 140 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
import React from 'react';
22
import { useTranslation } from 'react-i18next'
33

4+
import Box from '@mui/material/Box'
5+
import Chip from '@mui/material/Chip'
46
import Dialog from '@mui/material/Dialog'
5-
import DialogTitle from '@mui/material/DialogTitle'
67
import DialogContent from '@mui/material/DialogContent'
7-
import DialogActions from '@mui/material/DialogActions'
8+
import Divider from '@mui/material/Divider'
9+
import Stack from '@mui/material/Stack'
810
import Typography from '@mui/material/Typography'
9-
import List from '@mui/material/List'
10-
import ListItem from '@mui/material/ListItem'
11-
import ListItemText from '@mui/material/ListItemText'
1211

13-
import startCase from 'lodash/startCase'
12+
import isArray from 'lodash/isArray'
1413
import map from 'lodash/map'
14+
import startCase from 'lodash/startCase'
1515

16-
import Link from '../common/Link'
17-
16+
import { getScoreDetails, ScoreValueChip } from '../map-projects/Score'
17+
import CloseIconButton from '../common/CloseIconButton'
1818

19-
const SearchHighlightsDialog = ({onClose, highlight, score, raw_score, open}) => {
19+
const SearchHighlightsDialog = ({onClose, concept, rawScores, candidatesScore, open}) => {
2020
const { t } = useTranslation()
21+
const highlight = concept?.search_meta?.search_highlight || {}
22+
const { hasPercentile, bucketColor, rerankScore } = getScoreDetails(concept, candidatesScore)
23+
2124
return (
2225
<Dialog
2326
open={Boolean(open)}
2427
onClose={onClose}
2528
scroll='paper'
29+
fullWidth
30+
maxWidth='sm'
2631
sx={{
2732
'& .MuiDialog-paper': {
2833
backgroundColor: 'surface.n92',
@@ -33,81 +38,136 @@ const SearchHighlightsDialog = ({onClose, highlight, score, raw_score, open}) =>
3338
}
3439
}}
3540
>
36-
<DialogTitle sx={{p: 3, color: 'surface.dark', fontSize: '22px', textAlign: 'left'}}>
37-
{t('search.search_highlight')}
38-
</DialogTitle>
39-
<DialogContent style={{padding: 0}}>
40-
<List dense sx={{ width: '100%', bgcolor: 'surface.n92', padding: '0 10px', maxHeight: 700 }}>
41-
{
42-
map(highlight, (values, key) => (
43-
<React.Fragment key={key}>
44-
<ListItem>
45-
<ListItemText
41+
<DialogContent sx={{padding: 3, maxHeight: 700}}>
42+
<Stack spacing={2.5}>
43+
<Stack direction='row' justifyContent='space-between' alignItems='center' spacing={2}>
44+
<Typography sx={{color: 'surface.dark', fontSize: '22px', lineHeight: 1.2}}>
45+
{t('search.search_highlight')}
46+
</Typography>
47+
<CloseIconButton size='small' onClick={onClose} sx={{alignSelf: 'flex-start'}} />
48+
</Stack>
49+
50+
<Stack
51+
spacing={1.25}
52+
sx={{
53+
alignSelf: 'flex-start',
54+
minWidth: { xs: '100%', sm: 'auto' },
55+
maxWidth: '100%',
56+
padding: '12px 14px',
57+
borderRadius: '16px',
58+
backgroundColor: 'rgba(255, 255, 255, 0.4)',
59+
border: '1px solid rgba(0, 0, 0, 0.08)'
60+
}}
61+
>
62+
{
63+
hasPercentile &&
64+
<Stack
65+
direction='row'
66+
spacing={1.5}
67+
alignItems='center'
68+
justifyContent='space-between'
69+
sx={{ minWidth: { sm: '260px' } }}
70+
>
71+
<Typography sx={{fontSize: '12px', color: 'surface.light', fontWeight: 600, whiteSpace: 'nowrap'}}>
72+
{t('search.search_score')}
73+
</Typography>
74+
<ScoreValueChip size='small' bucketColor={bucketColor} label={rerankScore} />
75+
</Stack>
76+
}
77+
78+
<Stack spacing={0.75}>
79+
<Stack
80+
direction='row'
81+
spacing={1.5}
82+
alignItems='flex-start'
83+
justifyContent='space-between'
84+
sx={{ minWidth: { sm: '260px' } }}
85+
>
86+
<Typography sx={{fontSize: '12px', color: 'surface.light', fontWeight: 600, whiteSpace: 'nowrap', paddingTop: '4px'}}>
87+
{t('search.search_raw_score')}
88+
</Typography>
89+
{
90+
rawScores?.length ? (
91+
<Stack spacing={0.75} alignItems='flex-end'>
92+
{map(rawScores, (rawScore, index) => (
93+
<Stack
94+
key={`${rawScore.algorithm}-${rawScore.score}-${index}`}
95+
direction='row'
96+
spacing={0.75}
97+
alignItems='center'
98+
sx={{
99+
flexWrap: 'nowrap',
100+
width: 'fit-content'
101+
}}
102+
>
103+
<Chip size='small' label={rawScore.algorithm} variant='outlined' color='warning' />
104+
<ScoreValueChip
105+
size='small'
106+
showIndicator={false}
107+
label={rawScore.score}
108+
sx={{
109+
backgroundColor: 'rgba(0, 0, 0, 0.08)',
110+
color: 'surface.dark',
111+
fontWeight: 600
112+
}}
113+
/>
114+
</Stack>
115+
))}
116+
</Stack>
117+
) : (
118+
<Typography sx={{fontSize: '13px', color: 'surface.light', textAlign: 'right', paddingTop: '4px'}}>
119+
{t('common.none')}
120+
</Typography>
121+
)
122+
}
123+
</Stack>
124+
</Stack>
125+
</Stack>
126+
127+
<Divider />
128+
129+
<Stack spacing={1.5}>
130+
<Typography sx={{fontSize: '16px', color: 'surface.dark', fontWeight: 700}}>
131+
{t('search.matched_attributes')}
132+
</Typography>
133+
{
134+
map(highlight, (values, key) => (
135+
<Box key={key}>
136+
<Typography sx={{fontSize: '12px', color: 'surface.light', fontWeight: 600, marginBottom: 0.75}}>
137+
{startCase(key)}
138+
</Typography>
139+
<Stack
140+
spacing={isArray(values) && key === 'synonyms' ? 1.25 : 0.75}
46141
sx={{
47-
'.MuiListItemText-primary': {color: 'surface.dark', fontSize: '12px'},
48-
'.MuiListItemText-secondary': {color: 'default.light', fontSize: '14px'}
142+
paddingLeft: 1,
143+
'& b': {
144+
color: 'surface.dark'
145+
}
49146
}}
50-
primary={startCase(key)}
51-
secondary={
52-
<React.Fragment>
53-
{
54-
map(values, value => {
55-
value = value.replaceAll('<em>', '<b>').replaceAll('</em>', '</b>')
56-
return (
57-
<Typography
58-
key={value}
59-
component="span"
60-
sx={{ color: 'text.primary', display: 'inline-block' }}
61-
dangerouslySetInnerHTML={{__html: value}}
62-
/>
63-
)})
64-
}
65-
</React.Fragment>
147+
>
148+
{
149+
map(values, value => {
150+
const highlightedValue = value.replaceAll('<em>', '<b>').replaceAll('</em>', '</b>')
151+
return (
152+
<Typography
153+
key={value}
154+
sx={{
155+
color: 'text.primary',
156+
fontSize: key === 'synonyms' ? '12px' : '13px',
157+
lineHeight: key === 'synonyms' ? 0.95 : 1.3
158+
}}
159+
dangerouslySetInnerHTML={{__html: highlightedValue}}
160+
/>
161+
)
162+
})
66163
}
67-
/>
68-
</ListItem>
69-
</React.Fragment>
70-
))
71-
}
72-
<ListItem>
73-
<ListItemText
74-
sx={{
75-
'.MuiListItemText-primary': {color: 'surface.dark', fontSize: '12px'},
76-
'.MuiListItemText-secondary': {color: 'default.light', fontSize: '14px'}
77-
}}
78-
primary={t('search.search_score')}
79-
secondary={
80-
<Typography
81-
component="span"
82-
sx={{ color: 'text.primary', display: 'flex', fontWeight: 'bold' }}
83-
>
84-
{score}
85-
</Typography>
86-
}
87-
/>
88-
</ListItem>
89-
<ListItem>
90-
<ListItemText
91-
sx={{
92-
'.MuiListItemText-primary': {color: 'surface.dark', fontSize: '12px'},
93-
'.MuiListItemText-secondary': {color: 'default.light', fontSize: '14px'}
94-
}}
95-
primary={t('search.search_raw_score')}
96-
secondary={
97-
<Typography
98-
component="span"
99-
sx={{ color: 'text.primary', display: 'flex', fontWeight: 'bold' }}
100-
>
101-
{raw_score}
102-
</Typography>
103-
}
104-
/>
105-
</ListItem>
106-
</List>
164+
</Stack>
165+
</Box>
166+
))
167+
}
168+
</Stack>
169+
</Stack>
107170
</DialogContent>
108-
<DialogActions sx={{p: 3}}>
109-
<Link sx={{fontSize: '14px'}} label={t('common.close')} onClick={onClose} />
110-
</DialogActions>
111171
</Dialog>
112172
)
113173
}

src/i18n/locales/en/translations.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,11 @@
332332
"url_registry": "entries",
333333
"confidence": "Confidence",
334334
"score": "Score",
335-
"search_highlight": "Search Highlight",
335+
"search_highlight": "Match Details",
336336
"search_results": "Search Results",
337-
"search_score": "Search Score",
338-
"search_raw_score": "Search Raw Score",
337+
"search_score": "Unified Score",
338+
"search_raw_score": "Raw Scores",
339+
"matched_attributes": "Matched Attributes",
339340
"list_view": "List View",
340341
"table_view": "Table View"
341342
},

0 commit comments

Comments
 (0)