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

Commit c8417d5

Browse files
feat: LSDV-5242: TextArea Suggestions for LLM labeling (#1438)
* feat: LSDV-5242: MVP for dynamic LLM chat * Add "requesting" flag to main container during fetch * Add comments * Fix input height, increasing on every key press * Fix textarea regions `notifyFinishedDrawing` for continuous flow * Cleanup * More cleanup * Fix area access in TextArea `onChange` handler * Fix textarea `onChange` * Fix region deletion with `onChange` --------- Co-authored-by: hlomzik <hlomzik@users.noreply.github.com> Co-authored-by: Nick Skriabin <nr@fenelon.ru>
1 parent ccf87de commit c8417d5

File tree

7 files changed

+62
-18
lines changed

7 files changed

+62
-18
lines changed

src/common/TextArea/TextArea.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, MutableRefObject, RefObject, useCallback, useEffect, useRef } from 'react';
1+
import { FC, FocusEvent, MutableRefObject, RefObject, useCallback, useEffect, useRef } from 'react';
22
import { debounce } from 'lodash';
33
import { cn } from '../../utils/bem';
44
import { isMacOS } from '../../utils/utilities';
@@ -156,6 +156,13 @@ export const TextArea: FC<TextAreaProps> = ({
156156

157157

158158
return (
159-
<textarea ref={mergeRefs(textAreaRef, ref)} className={classList} rows={autoGrowRef.current.rows} onChange={onChange} onInput={onInput} {...props}></textarea>
159+
<textarea
160+
ref={mergeRefs(textAreaRef, ref)}
161+
className={classList}
162+
rows={autoGrowRef.current.rows}
163+
onChange={onChange}
164+
onInput={onInput}
165+
{...props}
166+
/>
160167
);
161168
};

src/components/App/App.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,9 @@ class App extends Component {
208208

209209
const viewingAll = as.viewingAllAnnotations || as.viewingAllPredictions;
210210

211+
// tags can be styled in config when user is awaiting for suggestions from ML backend
211212
const mainContent = (
212-
<Block name="main-content">
213+
<Block name="main-content" mix={store.awaitingSuggestions ? ['requesting'] : []}>
213214
{as.validation === null
214215
? this._renderUI(as.selectedHistory?.root ?? root, as)
215216
: this.renderConfigValidationException(store)}

src/components/HtxTextBox/HtxTextBox.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ export class HtxTextBox extends React.Component {
8585
};
8686

8787
updateHeight = throttle(() => {
88-
const borders = 2;
89-
const height = (this.inputRef.current?.scrollHeight || 0) + borders;
88+
const height = this.inputRef.current?.scrollHeight || 0;
9089

9190
if (height && height !== this.state.height) {
9291
this.setState({ height });

src/mixins/Regions.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,16 @@ const RegionsMixin = types
8080
return self.parent.findImageEntity(self.item_index ?? 0);
8181
},
8282

83-
getConnectedDynamicRegions(selfExcluding) {
83+
getConnectedDynamicRegions(excludeSelf) {
8484
const { regions = [] } = getRoot(self).annotationStore?.selected || {};
85+
const { type, labelName } = self;
8586

86-
return regions.filter(r => {
87-
if (selfExcluding && r === self) return false;
88-
return r.dynamic && r.type === self.type && r.labelName === self.labelName;
87+
const result = regions.filter(region => {
88+
if (excludeSelf && region === self) return false;
89+
return region.dynamic && region.type === type && region.labelName === labelName;
8990
});
91+
92+
return result;
9093
},
9194

9295
}))
@@ -225,6 +228,10 @@ const RegionsMixin = types
225228
self.perRegionFocusRequest = null;
226229
},
227230

231+
revokeSuggestion(){
232+
self.fromSuggestion = false;
233+
},
234+
228235
setHighlight(val) {
229236
self._highlighted = val;
230237
},
@@ -250,7 +257,7 @@ const RegionsMixin = types
250257
self.origin = 'prediction-changed';
251258
}
252259

253-
// everything above is related to dynamic preannotations
260+
// everything below is related to dynamic preannotations
254261
if (!self.dynamic || self.fromSuggestion) return;
255262

256263
clearTimeout(self.drawingTimeout);
@@ -260,7 +267,9 @@ const RegionsMixin = types
260267
const env = getEnv(self);
261268

262269
self.drawingTimeout = setTimeout(() => {
263-
env.events.invoke('regionFinishedDrawing', self, self.getConnectedDynamicRegions(destroy));
270+
const connectedRegions = self.getConnectedDynamicRegions(destroy);
271+
272+
env.events.invoke('regionFinishedDrawing', self, connectedRegions);
264273
}, timeout);
265274
}
266275
},

src/stores/Annotation/Annotation.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,15 @@ export const Annotation = types
10441044
self.acceptAllSuggestions();
10451045
} else {
10461046
self.suggestions.forEach((suggestion) => {
1047-
if (['richtextregion', 'text', 'textrange'].includes(suggestion.type)) {
1047+
// regions that can't be accepted in usual way, should be auto-accepted;
1048+
// textarea will have simple classification area with no type, so check result.
1049+
// @todo per-regions is tough thing here as they can be in generated result,
1050+
// connected to manual region, will check it later
1051+
const results = suggestion.results ?? [];
1052+
const onlyAutoAccept = ['richtextregion', 'text', 'textrange'].includes(suggestion.type)
1053+
|| results.findIndex(r => r.type === 'textarea') >= 0;
1054+
1055+
if (onlyAutoAccept) {
10481056
self.acceptSuggestion(suggestion.id);
10491057
if (isFF(FF_DEV_1284)) {
10501058
// This is necessary to prevent the occurrence of new steps in the history after updating objects at the end of current method
@@ -1057,7 +1065,7 @@ export const Annotation = types
10571065
if (!isFF(FF_DEV_1284)) {
10581066
history.freeze('richtext:suggestions');
10591067
}
1060-
self.objects.forEach(obj => obj.needsUpdate?.({ suggestions: true }));
1068+
self.names.forEach(tag => tag.needsUpdate?.({ suggestions: true }));
10611069
if (!isFF(FF_DEV_1284)) {
10621070
history.setReplaceNextUndoState(true);
10631071
history.unfreeze('richtext:suggestions');
@@ -1278,6 +1286,13 @@ export const Annotation = types
12781286
area.setValue(state);
12791287
});
12801288
self.suggestions.delete(id);
1289+
1290+
// hack to unlock sending textarea results
1291+
// to the ML backen every time
1292+
// it just sets `fromSuggestion` back to `false`
1293+
const isTextArea = area.results.findIndex(r => r.type === 'textarea') >= 0;
1294+
1295+
if (isTextArea) area.revokeSuggestion();
12811296
},
12821297

12831298
rejectSuggestion(id) {

src/tags/control/TextArea/TextArea.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ const Model = types.model({
215215
if (index < 0) return;
216216
self.regions.splice(index, 1);
217217
destroy(region);
218-
self.onChange();
218+
self.onChange(region);
219219
},
220220

221221
perRegionCleanup() {
@@ -229,8 +229,11 @@ const Model = types.model({
229229
return r;
230230
},
231231

232-
onChange() {
232+
onChange(area) {
233233
self.updateResult();
234+
const currentArea = (area ?? self.result?.area);
235+
236+
currentArea?.notifyDrawingFinished();
234237
},
235238

236239
validateValue(text) {
@@ -418,7 +421,17 @@ const HtxTextArea = observer(({ item }) => {
418421
);
419422
});
420423

421-
const HtxTextAreaResultLine = forwardRef(({ idx, value, readOnly, onChange, onDelete, onFocus, validate, control, collapsed }, ref) => {
424+
const HtxTextAreaResultLine = forwardRef(({
425+
idx,
426+
value,
427+
readOnly,
428+
onChange,
429+
onDelete,
430+
onFocus,
431+
validate,
432+
control,
433+
collapsed,
434+
}, ref) => {
422435
const rows = parseInt(control.rows);
423436
const isTextarea = rows > 1;
424437
const [stateValue, setStateValue] = useState(value ?? '');

tests/functional/specs/image_segmentation/tools/rect3point.cy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ImageView, LabelStudio } from '@heartexlabs/ls-test/helpers/LSF';
22
import { rect3Config, simpleImageData } from '../../../data/image_segmentation/tools/rect3point';
3-
import { FF_DEV_2132, FF_DEV_3793, FF_LSDV_4673 } from '../../../../../src/utils/feature-flags';
3+
import { FF_DEV_2132, FF_DEV_3793 } from '../../../../../src/utils/feature-flags';
44

55
describe('Rect3Point tool', () => {
66
beforeEach(() => {
@@ -173,4 +173,4 @@ describe('Rect3Point tool', () => {
173173
expect(result.value.rotation).to.be.eq(0);
174174
});
175175
});
176-
});
176+
});

0 commit comments

Comments
 (0)