Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

fix: LEAP-31: Fix functioning of auto annotations #1605

Merged
merged 14 commits into from
Nov 21, 2023
2 changes: 1 addition & 1 deletion src/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class App extends Component {
{this.renderRelations(as.selected)}
</Elem>
{(!isFF(FF_DEV_3873)) && getRoot(as).hasInterface('infobar') && this._renderInfobar(as)}
{as.selected.onlyTextObjects === false && (
{as.selected.hasSuggestionsSupport && (
<DynamicPreannotationsControl />
)}
</Block>
Expand Down
4 changes: 4 additions & 0 deletions src/mixins/AreaMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ export const AreaMixinBase = types
return (!isFF(FF_LSDV_4930) || !self.hidden)
&& self.parent?.selectionArea?.isActive ? self.parent.selectionArea.intersectsBbox(self.bboxCoords) : false;
},

get supportSuggestions() {
return self.object.supportSuggestions;
},
}))
.volatile(() => ({
// selected: false,
Expand Down
44 changes: 42 additions & 2 deletions src/mixins/Regions.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,39 @@ const RegionsMixin = types

const result = regions.filter(region => {
if (excludeSelf && region === self) return false;
return region.dynamic && region.type === type && region.labelName === labelName;
const canBePartOfNotification = self.supportSuggestions ? self.dynamic : true;

return canBePartOfNotification
&& region.type === type
&& region.labelName === labelName
&& region.results?.[0]?.to_name === self.results?.[0]?.to_name;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It potentially compare undefined === undefined here. Could it lead to any unexpected issues?

Copy link
Contributor Author

@Gondragos Gondragos Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just checked that. It seems that the only way to get undefined === undefined is collecting connected regions in the case with config that contains two classification tags with the same to_name. I'm not sure if we need to group them but it's anyhow is better than it was.

});

return result;
},

// Indicates that it is not temporary region created just to display data like Textarea's one
// and is not a suggestion
get isRealRegion() {
return self.annotation?.areas?.has(self.id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually smart. Love it.

},

get shouldNotifyDrawingFinished() {
// extra calls on destroying will be skipped
// @see beforeDestroy action
if (!self.isRealRegion) return false;
if (self.annotation.isSuggestionsAccepting) return false;
// There are two modes:
// If object tag support suggestions - the region should be marked as a dynamic one to make notifications
// If object tag doesn't support suggestions - every region works as dynamic with auto suggestions
const canBeReasonOfNotification = self.supportSuggestions ? self.dynamic && !self.fromSuggestion : true;

const isSmartEnabled = self.results.some(r => {
return r.from_name.smartEnabled;
});

return isSmartEnabled && canBeReasonOfNotification;
},
}))
.actions(self => {
return {
Expand All @@ -114,6 +141,19 @@ const RegionsMixin = types
},

beforeDestroy() {
// beforeDestroy may be called by accident for Textarea and etc. as part of updateObjects action
// in that case the region already has no results

// The other bad behaviour is that beforeDestroy may be called on accepting suggestions 'cause they are deleting in that case

// So if you see this bad thing during debugging - now you know why
// and why we need this check
if (self.isRealRegion) {
return self.beforeDestroyArea();
}
},

beforeDestroyArea() {
self.notifyDrawingFinished({ destroy: true });
},

Expand Down Expand Up @@ -258,7 +298,7 @@ const RegionsMixin = types
}

// everything below is related to dynamic preannotations
if (!self.dynamic || self.fromSuggestion) return;
if (!self.shouldNotifyDrawingFinished) return;

clearTimeout(self.drawingTimeout);

Expand Down
5 changes: 5 additions & 0 deletions src/regions/Area.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ const ClassificationArea = types.compose(
// true only for global classifications
classification: true,
})
.views(() => ({
get supportSuggestions() {
return false;
},
}))
.actions(() => ({
serialize: () => ({}),
})),
Expand Down
36 changes: 16 additions & 20 deletions src/stores/Annotation/Annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,10 @@ export const Annotation = types
return dataExists && pkExists;
},

get onlyTextObjects() {
return self.objects.reduce((res, obj) => {
return res && ['text', 'hypertext', 'paragraphs'].includes(obj.type);
}, true);
get hasSuggestionsSupport() {
return self.objects.some((obj) => {
return obj.supportSuggestions;
});
},

isReadOnly() {
Expand All @@ -253,6 +253,7 @@ export const Annotation = types
draftSelected: false,
autosaveDelay: 5000,
isDraftSaving: false,
isSuggestionsAccepting: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please add a comment describing this flag? from the name alone it's not very clear what it supposed to do

submissionStarted: 0,
versions: {},
resultSnapshot: '',
Expand Down Expand Up @@ -1058,6 +1059,7 @@ export const Annotation = types
suggestions: true,
});

self.isSuggestionsAccepting = true;
if (getRoot(self).autoAcceptSuggestions) {
if (isFF(FF_DEV_1284)) {
self.history.setReplaceNextUndoState(true);
Expand All @@ -1066,14 +1068,10 @@ export const Annotation = types
} else {
self.suggestions.forEach((suggestion) => {
// regions that can't be accepted in usual way, should be auto-accepted;
// textarea will have simple classification area with no type, so check result.
// @todo per-regions is tough thing here as they can be in generated result,
// connected to manual region, will check it later
const results = suggestion.results ?? [];
const onlyAutoAccept = ['richtextregion', 'text', 'textrange'].includes(suggestion.type)
|| results.findIndex(r => r.type === 'textarea') >= 0;

if (onlyAutoAccept) {
const supportSuggestions = suggestion.supportSuggestions;

// If we cannot display suggestions on object/control then just accept them
if (!supportSuggestions) {
self.acceptSuggestion(suggestion.id);
if (isFF(FF_DEV_1284)) {
// This is necessary to prevent the occurrence of new steps in the history after updating objects at the end of current method
Expand All @@ -1082,6 +1080,7 @@ export const Annotation = types
}
});
}
self.isSuggestionsAccepting = false;

if (!isFF(FF_DEV_1284)) {
history.freeze('richtext:suggestions');
Expand Down Expand Up @@ -1316,6 +1315,11 @@ export const Annotation = types
}
}
} else {
// @todo: there is a strange behaviour that should be documented somewhere
// On serialization we use area id as result id to save it somewhere
// and on deserialization we use result id as area id
// but when we use suggestions we should keep in mind that we need to do it manually or use serialized data instead
// or we can get weird regions duplication in some cases
const area = self.areas.get(item.cleanId);

if (area) {
Expand All @@ -1337,14 +1341,6 @@ export const Annotation = types
});
self.suggestions.delete(id);

// hack to unlock sending textarea results
// to the ML backen every time
// it just sets `fromSuggestion` back to `false`
const isTextArea = area.results.findIndex(r => r.type === 'textarea') >= 0;

// This is temporary exception until we find the way to do it right
// and this was done to keep notifications on prompt editing or fixing answer from ML backend
if (isTextArea) area.revokeSuggestion();
},

rejectSuggestion(id) {
Expand Down
3 changes: 1 addition & 2 deletions src/stores/AppStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import Settings from './SettingsStore';
import Task from './TaskStore';
import { UserExtended } from './UserStore';
import { UserLabels } from './UserLabels';
import { FF_DEV_1536, FF_DEV_2715, FF_LLM_EPIC, FF_LSDV_4620_3_ML, FF_LSDV_4998, isFF } from '../utils/feature-flags';
import { FF_DEV_1536, FF_DEV_2715, FF_LSDV_4620_3_ML, FF_LSDV_4998, isFF } from '../utils/feature-flags';
import { CommentStore } from './Comment/CommentStore';
import { destroy as destroySharedStore } from '../mixins/SharedChoiceStore/mixin';

Expand Down Expand Up @@ -225,7 +225,6 @@ export default types
return self.forceAutoAnnotation || self._autoAnnotation;
},
get autoAcceptSuggestions() {
if (isFF(FF_LLM_EPIC)) return true;
return self.forceAutoAcceptSuggestions || self._autoAcceptSuggestions;
},
}))
Expand Down
9 changes: 8 additions & 1 deletion src/tags/control/Base.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { types } from 'mobx-state-tree';
import { getRoot, types } from 'mobx-state-tree';
import { FF_DEV_3391, FF_SNAP_TO_PIXEL, isFF } from '../../utils/feature-flags';
import { BaseTag } from '../TagBase';
import { SNAP_TO_PIXEL_MODE } from '../../components/ImageView/Image';
Expand Down Expand Up @@ -55,6 +55,13 @@ const ControlBase = types.model({
}
return point;
},
get smartEnabled() {
const smart = self.smart ?? false;
const autoAnnotation = getRoot(self)?.autoAnnotation ?? false;

// @todo: Not sure why smartonly ignores autoAnnotation; It was like this from the beginning
return (autoAnnotation && smart) || self.smartonly || false;
},
}));

export default types.compose(ControlBase, BaseTag);
6 changes: 1 addition & 5 deletions src/tags/control/TextArea/TextArea.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Button from 'antd/lib/button/index';
import Form from 'antd/lib/form/index';
import Input from 'antd/lib/input/index';
import { observer } from 'mobx-react';
import { destroy, getRoot, isAlive, types } from 'mobx-state-tree';
import { destroy, isAlive, types } from 'mobx-state-tree';

import InfoModal from '../../../components/Infomodal/Infomodal';
import Registry from '../../../core/Registry';
Expand Down Expand Up @@ -235,10 +235,6 @@ const Model = types.model({
onChange(area) {
self.updateResult();
const currentArea = (area ?? self.result?.area);

if (getRoot(self).autoAnnotation) {
currentArea.makeDynamic();
}

currentArea?.notifyDrawingFinished();
},
Expand Down
5 changes: 4 additions & 1 deletion src/tags/object/Base.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ const ObjectBase = types
}),
// TODO there should be a better way to force an update
_needsUpdate: types.optional(types.number, 0),
isObjectTag: true,
})
.volatile(() => ({
isObjectTag: true,
supportSuggestions: false,
}))
.views(self => ({
/**
* A list of all related regions
Expand Down
1 change: 1 addition & 0 deletions src/tags/object/Image/Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ const Model = types.model({
selectionArea: types.optional(ImageSelection, { start: null, end: null }),
}).volatile(() => ({
currentImage: undefined,
supportSuggestions: true,
})).views(self => ({
get store() {
return getRoot(self);
Expand Down
8 changes: 0 additions & 8 deletions src/tags/object/Paragraphs/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -500,10 +500,6 @@ const ParagraphsLoadingModel = types.model()
for (const range of ranges) {
const area = self.annotation.createResult(range, labels, control, self);

if (getRoot(self).autoAnnotation) {
area.makeDynamic();
}

area.setText(range.text);

area.notifyDrawingFinished();
Expand All @@ -526,10 +522,6 @@ const ParagraphsLoadingModel = types.model()
const labels = { [control.valueType]: control.selectedValues() };
const area = self.annotation.createResult(range, labels, control, self);

if (getRoot(self).autoAnnotation) {
area.makeDynamic();
}

area.setText(range.text);

area.notifyDrawingFinished();
Expand Down
1 change: 0 additions & 1 deletion src/tags/object/RichText/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ class RichTextPieceView extends Component {
normedRange._range = range;
normedRange.text = selectionText;
normedRange.isText = item.type === 'text';
normedRange.dynamic = this.props.store.autoAnnotation;
item.addRegion(normedRange, this.doubleClickSelection);
}, {
window: rootEl?.contentWindow ?? window,
Expand Down
10 changes: 2 additions & 8 deletions src/tools/Base.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getEnv, getRoot, getSnapshot, getType, types } from 'mobx-state-tree';
import { getEnv, getSnapshot, getType, types } from 'mobx-state-tree';
import { observer } from 'mobx-react';
import React from 'react';
import { Tool } from '../components/Toolbar/Tool';
Expand Down Expand Up @@ -63,13 +63,7 @@ const BaseTool = types
return null;
},
get smartEnabled() {
const smart = self.control?.smart || false;
const autoAnnotation = self.control ? getRoot(self.control)?.autoAnnotation ?? false : false;

return (autoAnnotation && smart) || self.smartOnly;
},
get smartOnly() {
return self.control?.smartonly ?? false;
return self.control?.smartEnabled ?? false;
},
};
})
Expand Down
2 changes: 1 addition & 1 deletion src/tools/Manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ToolsManager {
}

addTool(toolName, tool, removeDuplicatesNamed = null, prefix = guidGenerator()) {
if (tool.smart && tool.smartOnly) return;
if (tool.smart && tool.control?.smartonly) return;
// todo: It seems that key is used only for storing,
// but not for finding tools, so may be there might
// be an array instead of an object
Expand Down
Loading