Skip to content

Commit

Permalink
feat(#950): using record search_keywords for highlighting (#1235)
Browse files Browse the repository at this point in the history
* feat(#950): using record search_keywords for highlighting

* feat(highlight): keywords for text-classification

* feat(highlight): keywords for text2text

* test: update snapshots

* fix: compute search keywords for old datasets, based on 'words' field

(cherry picked from commit 9e11933)

fix(#950):  improve highlight for multi terms searches (#1278)

* fix(hightlight): merge adjacent terms

* refactor: apply multi-keyword search in text

* feat: merge highlighted phrases

* feat: parse keywords as entire words

* fix(highlight): include hightlight info at visual token level

* chore: lint

* fix: escape html before apply highligh span

* fix calc whitespace for highlighted entities

* fix: parse middle highlighted tokens

* refactor: highligh on inputs

* test: update tests

Co-authored-by: LeireA <leire@recogn.ai>
(cherry picked from commit 3a32334)

chore(#1235): fix highlight function name in explain (#1316)

Closes #1315

(cherry picked from commit 41b3321)
  • Loading branch information
frascuchon committed Mar 30, 2022
1 parent bb7d93e commit 47616bf
Show file tree
Hide file tree
Showing 20 changed files with 244 additions and 155 deletions.
12 changes: 10 additions & 2 deletions frontend/components/core/ReTableInfo.vue
Expand Up @@ -111,8 +111,16 @@
{{ itemValue(item, column) | percent }}
</span>
<span v-else-if="column.type === 'array'">
<p v-for="(arrayItem, index) in itemValue(item, column)" :key="index">
{{arrayItem}}{{index + 1 === itemValue(item, column).length ? '' : ','}}
<p
v-for="(arrayItem, index) in itemValue(item, column)"
:key="index"
>
{{ arrayItem
}}{{
index + 1 === itemValue(item, column).length
? ""
: ","
}}
</p>
</span>
<span v-else-if="column.type === 'object'">
Expand Down
19 changes: 9 additions & 10 deletions frontend/components/text-classifier/results/RecordExplain.vue
Expand Up @@ -33,19 +33,18 @@
<script>
export default {
props: {
queryText: {
type: String,
default: undefined,
record: {
type: Object,
required: true,
},
explain: {
type: Array,
},
predicted: {
type: String,
default: undefined,
},
}
},
computed: {
predicted() {
return this.record.predicted;
},
explainFormatted() {
// TODO ALLOW FOR MULTI LABEL
return this.explain.map((token) => {
Expand All @@ -58,8 +57,8 @@ export default {
percent = Math.round(Math.log10(percent) ** p * s);
}
return {
text: this.queryText
? this.$highlightSearch(this.queryText, token.token)
text: this.record.search_keywords
? this.$highlightKeywords(token.token, this.record.search_keywords)
: token.token,
percent: percent.toString(),
grad,
Expand Down
28 changes: 11 additions & 17 deletions frontend/components/text-classifier/results/RecordInputs.vue
Expand Up @@ -26,11 +26,10 @@
<span class="record__key">{{ index }}:</span>
<LazyRecordExplain
v-if="explanation"
:predicted="predicted"
:query-text="queryText"
:record="record"
:explain="explanation[index]"
/>
<LazyRecordString v-else :query-text="queryText" :text="text" />
<LazyRecordString v-else :record="record" :text="text" />
</span>
</span>
</div>
Expand All @@ -47,27 +46,22 @@
<script>
export default {
props: {
data: {
record: {
type: Object,
required: true,
},
queryText: {
type: String,
},
predicted: {
type: String,
default: undefined,
},
explanation: {
type: Object,
default: () => undefined,
},
required: true
}
},
data: () => ({
showFullRecord: false,
scrollHeight: undefined,
}),
computed: {
data() {
return this.record.inputs;
},
explanation() {
return this.record.explanation;
},
visibleRecordHeight() {
return this.$mq === "lg" ? 468 : 174;
},
Expand Down
19 changes: 13 additions & 6 deletions frontend/components/text-classifier/results/RecordString.vue
Expand Up @@ -21,7 +21,7 @@
<div v-for="item in text" :key="item.index">
<span
class="record__content"
v-html="$highlightSearch(queryText, item)"
v-html="$highlightKeywords(item, keywords)"
>
</span>
</div>
Expand All @@ -30,22 +30,28 @@
<span
v-else
class="record__content"
v-html="$highlightSearch(queryText, text)"
v-html="$highlightKeywords(text, keywords)"
>
</span>
</div>
</template>

<script>
export default {
props: {
record: {
type: Object,
required: true,
},
text: {
type: [String, Array],
required: true,
},
queryText: {
type: String,
default: undefined,
},
},
computed: {
keywords() {
return this.record.search_keywords
}
},
methods: {
isList(record) {
Expand All @@ -54,6 +60,7 @@ export default {
},
};
</script>

<style lang="scss" scoped>
.record {
&__content {
Expand Down
Expand Up @@ -20,12 +20,7 @@
<!-- annotation labels and prediction status -->
<div class="record--left">
<!-- record text -->
<RecordInputs
:predicted="record.predicted"
:data="record.inputs"
:explanation="record.explanation"
:query-text="dataset.query.text"
/>
<RecordInputs :record="record" />
<ClassifierAnnotationArea
v-if="annotationEnabled"
:dataset="dataset"
Expand Down
20 changes: 11 additions & 9 deletions frontend/components/text2text/results/RecordStringText2Text.vue
Expand Up @@ -20,7 +20,7 @@
ref="list"
:class="showFullRecord ? 'record__expanded' : 'record__collapsed'"
>
<span class="record__content" v-html="$highlightSearch(queryText, text)">
<span class="record__content" v-html="$highlightKeywords(text, keywords)">
</span>
<a
href="#"
Expand All @@ -34,20 +34,22 @@
<script>
export default {
props: {
text: {
type: [String, Array],
required: true,
},
queryText: {
type: String,
default: undefined,
},
record: {
type: Object,
required: true
}
},
data: () => ({
showFullRecord: false,
scrollHeight: undefined,
}),
computed: {
text() {
return this.record.text;
},
keywords() {
return this.record.search_keywords;
},
visibleRecordHeight() {
return this.$mq === "lg" ? 570 : 260;
},
Expand Down
5 changes: 1 addition & 4 deletions frontend/components/text2text/results/RecordText2Text.vue
Expand Up @@ -18,10 +18,7 @@
<template>
<div class="record">
<div class="record--left record__item">
<record-string-text-2-text
:query-text="dataset.query.text"
:text="record.text"
/>
<record-string-text-2-text :record="record" />
<div>
<text-2-text-list
ref="list"
Expand Down
33 changes: 17 additions & 16 deletions frontend/components/token-classifier/results/EntityHighlight.vue
Expand Up @@ -19,12 +19,7 @@
<span
@mouseenter="showTooltip = true"
@mouseleave="showTooltip = false"
:class="[
'highlight',
span.origin,
// isText ? '' : 'highlight--block',
annotationEnabled ? 'editable' : null,
]"
:class="['highlight', span.origin, annotationEnabled ? 'editable' : null]"
><span
v-for="(token, i) in span.tokens"
:key="i"
Expand All @@ -36,11 +31,7 @@
]"
@click="openTagSelector"
@dblclick="removeEntity"
v-html="
`${$highlightSearch(dataset.query.text, token.text)}${
token.hasSpaceAfter && i + 1 !== span.tokens.length ? ' ' : ''
}`
"
v-html="visualizeToken(token, i)"
></span>
<svgicon
class="remove-button"
Expand All @@ -53,6 +44,7 @@
<lazy-text-span-tooltip v-if="showTooltip" :span="span" />
</span>
</template>

<script>
import "assets/icons/cross";
Expand All @@ -69,6 +61,10 @@ export default {
type: Object,
required: true,
},
record: {
type: Object,
required: true,
}
},
data: () => {
return {
Expand All @@ -79,9 +75,6 @@ export default {
};
},
computed: {
// isText() {
// return this.text.replace(/\s/g, "").length;
// },
annotationEnabled() {
return this.dataset.viewSettings.viewMode === "annotate";
},
Expand All @@ -107,19 +100,26 @@ export default {
}, this.singleClickDelay);
}
},
visualizeToken(token, i) {
let text = token.highlighted
? this.$htmlHighlightText(token.text)
: this.$htmlText(token.text);
return `${text}${token.hasSpaceAfter && i + 1 !== this.span.tokens.length ? " " : ""}`;
},
},
};
</script>

<style lang="scss" scoped>
.whitespace {
margin-right: 3.5px;
}
.highlight {
@include font-size(0);
line-height: 1em;
position: relative;
cursor: default;
// display: inline-flex;
cursor: default; // display: inline-flex;
border-radius: 2px;
padding: 0;
&.editable {
Expand All @@ -146,6 +146,7 @@ export default {
z-index: 5;
}
}
.remove-button {
opacity: 0;
z-index: -1;
Expand Down
Expand Up @@ -21,18 +21,22 @@
<record-token-classification-annotation
:dataset="dataset"
:record="record"
:visualTokens="visualTokens"
v-if="annotationEnabled"
/>
<record-token-classification-exploration
:dataset="dataset"
:record="record"
:visualTokens="visualTokens"
v-else
/>
</div>
</div>
</template>

<script>
import { indexOf, length } from "stringz";
export default {
props: {
dataset: {
Expand All @@ -48,6 +52,44 @@ export default {
annotationEnabled() {
return this.dataset.viewSettings.viewMode === "annotate";
},
visualTokens() {
// This is used for both, annotation ad exploration components
const recordHasEmoji = this.record.text.containsEmoji;
const searchKeywordsSpans = this.$keywordsSpans(
this.record.text,
this.record.search_keywords
);
const { visualTokens } = this.record.tokens.reduce(
({ visualTokens, startPosition }, token) => {
const start = recordHasEmoji
? indexOf(this.record.text, token, startPosition)
: this.record.text.indexOf(token, startPosition);
const end = start + (recordHasEmoji ? length(token) : token.length);
const hasSpaceAfter = this.record.text.slice(end, end + 1) === " ";
let highlighted = false;
for (let highlight of searchKeywordsSpans) {
if (highlight.start <= start && highlight.end >= end) {
highlighted = true;
break;
}
}
return {
visualTokens: [
...visualTokens,
{ start, end, highlighted, text: token, hasSpaceAfter },
],
startPosition: end,
};
},
{
visualTokens: [],
startPosition: 0,
}
);
return visualTokens;
},
},
};
</script>
Expand Down

0 comments on commit 47616bf

Please sign in to comment.