Show LabelBOT popup despite no suggestions#1478
Conversation
There was a problem hiding this comment.
Pull request overview
This PR changes LabelBOT’s empty-result behavior so annotations can still be created when no suggestions are found, then prompts the user to manually choose a label or dismiss the temporary annotation.
Changes:
- Backend LabelBOT now returns an empty label list instead of throwing a 404.
- Image/video annotation creation can save annotations without immediately attaching a label.
- LabelBOT popup now displays an empty-suggestions message and supports manual label selection/deletion flow.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
app/Services/LabelBot/LabelBotService.php |
Returns an empty suggestion array when no similar labels are found. |
app/Http/Controllers/Api/ImageAnnotationController.php |
Skips label authorization/attachment when no label is available. |
app/Http/Controllers/Api/VideoAnnotationController.php |
Mirrors the image annotation empty-label persistence behavior. |
resources/assets/js/annotations/components/labelbotPopup.vue |
Adds no-suggestions UI and temporary annotation deletion behavior. |
resources/assets/js/annotations/annotatorContainer.vue |
Refreshes annotations after manually attaching a label when no previous label existed. |
resources/assets/sass/annotations/components/_labelbot-popup.scss |
Adds styling for the no-suggestions popup message. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!this.selectedLabel || this.selectedLabel.id !== label.id) { | ||
| this.dismissLabelbotAnnotation = false; | ||
| this.$emit('update', {label: label, annotation: this.annotation}); |
| if (this.noLabels && this.dismissLabelbotAnnotation) { | ||
| this.deleteLabelAnnotation(); |
|
|
||
| if (empty($topNLabels)) { | ||
| throw new NotFoundHttpException("LabelBOT could not find similar annotations."); | ||
| return []; |
| $response->assertSuccessful(); | ||
|
|
||
| // We expect an empty array | ||
| $response->assertJsonFragment([]); |
| this.emitClose(); | ||
| Events.emit('labelbot.chose_label_1'); | ||
| if (this.noLabels) { | ||
| this.deleteLabelAnnotation(); |
| if (this.noLabels && this.deletePendingLabelbotAnnotation) { | ||
| this.deleteLabelAnnotation(); |
| if ($label) { | ||
| VideoAnnotationLabel::create([ | ||
| 'label_id' => $label->id, | ||
| 'user_id' => $request->user()->id, | ||
| 'annotation_id' => $annotation->id, | ||
| ]); | ||
| } |
|
Please request my review again once you are through with the AI. |
|
@mzur While trying to implement this for videos, I found some other issues:
Screencast.from.2026-06-02.13-58-18.webm
Screencast.from.2026-06-02.14-04-56.webmOr was that intentional for 2 ? Otherwise, I could try to fix it here. |
| </div> | ||
| <ul class="labelbot-labels"> | ||
| <li | ||
| <li v-if="noLabels" class="labelbot-popup__message"> |
There was a problem hiding this comment.
| <li v-if="noLabels" class="labelbot-popup__message"> | |
| <li v-if="noLabels" class="labelbot-popup__message text-muted"> |
| <li | ||
| <li v-if="noLabels" class="labelbot-popup__message"> | ||
| <p> | ||
| LabelBOT could not find similar annotations. Enter a label manually below, or close this popup to delete the annotation. |
There was a problem hiding this comment.
| LabelBOT could not find similar annotations. Enter a label manually below, or close this popup to delete the annotation. | |
| LabelBOT could not find similar annotations. Enter a label below, or close this popup to delete the annotation. |
| // Delete saved unlabeled LabelBOT annotations | ||
| // Such annotations are only created when LabelBOT returns no results | ||
| // and the user refreshes/closes the BIIGLE session without interacting with the empty LabelBOT popup. | ||
| if ($annotation->labels->isEmpty()) { | ||
| $annotation->delete(); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
We can't do it like this. The index endpoint must not delete anything. We have to update the behavior of the store endpoint instead.
| $annotation->save(); | ||
| $annotationLabel = new ImageAnnotationLabel; | ||
| $annotationLabel->label_id = $label->id; | ||
| $annotationLabel->user_id = $request->user()->id; | ||
| $annotationLabel->confidence = $request->input('confidence'); | ||
| $annotation->labels()->save($annotationLabel); | ||
| if ($label) { | ||
| $annotationLabel = new ImageAnnotationLabel; | ||
| $annotationLabel->label_id = $label->id; | ||
| $annotationLabel->user_id = $request->user()->id; | ||
| $annotationLabel->confidence = $request->input('confidence'); | ||
| $annotation->labels()->save($annotationLabel); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Without any label, no annotation should be created here. We could instead respond with a "204 No Content" to signal the UI that no label could be found. The UI can then keep the temporary annotation. If the user tries again with a manually chosen label, the annotation could be saved and the temporary annotation replaced. if the popup is closed then the temporary annotation is just discarded in the UI.
| if ($label) { | ||
| $this->authorize('attach-label', [$annotation, $label]); | ||
| } | ||
|
|
||
| $annotation = DB::transaction(function () use ($annotation, $request, $label) { | ||
| $annotation->save(); | ||
| VideoAnnotationLabel::create([ | ||
| 'label_id' => $label->id, | ||
| 'user_id' => $request->user()->id, | ||
| 'annotation_id' => $annotation->id, | ||
| ]); | ||
| if ($label) { | ||
| VideoAnnotationLabel::create([ | ||
| 'label_id' => $label->id, | ||
| 'user_id' => $request->user()->id, | ||
| 'annotation_id' => $annotation->id, | ||
| ]); | ||
| } |
There was a problem hiding this comment.
Same here. If no label is found, don't create an annotation and respond with a 204. Then handle the rest in the UI.
207345e to
ac0a62a
Compare
When LabelBOT returns no suggestions, an empty array will be sent to the front end. The LabelBOT popup will show a message to type a label in the typeahead. For now the temp annotation will be saved in the database without a label and it will have the editing style in the front end until it's confirmed with a label, if not then it will be deleted.