From 40d44bb9c110c5c28e4c46a3c9c2ed6cdf68e69b Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 13 Dec 2024 10:34:06 +0000 Subject: [PATCH 01/30] feat: DIA-1748: 'Generate labels with AI' button in Project, in SC --- label_studio/data_import/functions.py | 6 +++++- label_studio/projects/models.py | 9 ++++++++- label_studio/tasks/serializers.py | 5 +++++ .../src/components/DataManager/Toolbar/Toolbar.jsx | 1 - .../src/components/DataManager/Toolbar/instruments.jsx | 4 ++++ 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/label_studio/data_import/functions.py b/label_studio/data_import/functions.py index d0c4262412a4..10cabdea3381 100644 --- a/label_studio/data_import/functions.py +++ b/label_studio/data_import/functions.py @@ -19,7 +19,11 @@ def async_import_background( - import_id, user_id, recalculate_stats_func: Optional[Callable[..., None]] = None, **kwargs + import_id, + user_id, + recalculate_stats_func: Optional[Callable[..., None]] = None, + callback_func: Optional[Callable[..., None]] = None, + **kwargs ): with transaction.atomic(): try: diff --git a/label_studio/projects/models.py b/label_studio/projects/models.py index 8c10b25e8227..8c0c1c6fbcb1 100644 --- a/label_studio/projects/models.py +++ b/label_studio/projects/models.py @@ -3,7 +3,6 @@ import json import logging from typing import Any, Mapping, Optional - from annoying.fields import AutoOneToOneField from core.feature_flags import flag_set from core.label_config import ( @@ -46,6 +45,7 @@ annotate_useful_annotation_number, ) from projects.functions.utils import make_queryset_from_iterable +from projects.signals import ProjectSignals from tasks.models import ( Annotation, AnnotationDraft, @@ -658,6 +658,10 @@ def display_count(count: int, type: str) -> Optional[str]: def _label_config_has_changed(self): return self.label_config != self.__original_label_config + + @property + def label_config_is_not_default(self): + return self.label_config != Project._meta.get_field('label_config').default def should_none_model_version(self, model_version): """ @@ -742,6 +746,9 @@ def save(self, *args, update_fields=None, recalc=True, **kwargs): if self._label_config_has_changed(): self.__original_label_config = self.label_config + # if tasks are already imported, emit signal that project is configured and ready for labeling + if self.num_tasks > 0: + ProjectSignals.post_label_config_and_import_tasks.send(sender=Project, project=self) super(Project, self).save(*args, update_fields=update_fields, **kwargs) diff --git a/label_studio/tasks/serializers.py b/label_studio/tasks/serializers.py index 6fc612ce5a5b..4a32e92bc849 100644 --- a/label_studio/tasks/serializers.py +++ b/label_studio/tasks/serializers.py @@ -391,6 +391,7 @@ def create(self, validated_data): self.post_process_annotations(user, db_annotations, 'imported') self.post_process_tasks(self.project.id, [t.id for t in self.db_tasks]) + self.post_process_custom_callback(self.project.id, user) if flag_set('fflag_feat_back_lsdv_5307_import_reviews_drafts_29062023_short', user=ff_user): with transaction.atomic(): @@ -585,6 +586,10 @@ def post_process_tasks(user, db_tasks): @staticmethod def add_annotation_fields(body, user, action): return body + + @staticmethod + def post_process_custom_callback(project_id): + pass class Meta: model = Task diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/Toolbar.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/Toolbar.jsx index c34e5486fe94..f8f4c6d0b515 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/Toolbar.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/Toolbar.jsx @@ -21,7 +21,6 @@ export const Toolbar = injector( {section.map((instrument, i) => { const Instrument = store.SDK.getInstrument(instrument); - return Instrument ? ( ) : null; diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/instruments.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/instruments.jsx index 076c7ffcb333..7f395a317b54 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/instruments.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/instruments.jsx @@ -15,6 +15,7 @@ import { LoadingPossum } from "./LoadingPossum"; import { OrderButton } from "./OrderButton"; import { RefreshButton } from "./RefreshButton"; import { ViewToggle } from "./ViewToggle"; +import { InfoBanner } from "./InfoBanner"; const style = { minWidth: "110px", @@ -118,6 +119,9 @@ export const instruments = { "error-box": () => { return ; }, + "info-banner": () => { + return ; + }, "import-button": ({ size }) => { return ( From 470f2c4057e4e4a747d5326a3a1311199c7584af Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 13 Dec 2024 10:35:48 +0000 Subject: [PATCH 02/30] Add signals.py --- label_studio/projects/signals.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 label_studio/projects/signals.py diff --git a/label_studio/projects/signals.py b/label_studio/projects/signals.py new file mode 100644 index 000000000000..8f36230565b4 --- /dev/null +++ b/label_studio/projects/signals.py @@ -0,0 +1,17 @@ +from django.dispatch import Signal + + +class ProjectSignals: + """ + Signals for project: implements observer pattern for custom signals. + Example: + + # publisher + ProjectSignals.my_signal.send(sender=self, project=project) + + # observer + @receiver(ProjectSignals.my_signal) + def my_observer(sender, **kwargs): + ... + """ + post_label_config_and_import_tasks = Signal() From 77387b11f9c7fc7ed38de90c9f6cab96130d4d52 Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 13 Dec 2024 10:36:29 +0000 Subject: [PATCH 03/30] Add infobanner --- .../DataManager/Toolbar/InfoBanner.jsx | 20 ++++++++++++ .../DataManager/Toolbar/InfoBanner.scss | 32 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.jsx create mode 100644 web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.scss diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.jsx new file mode 100644 index 000000000000..91eb34530f50 --- /dev/null +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.jsx @@ -0,0 +1,20 @@ +import { inject } from "mobx-react"; +import { Block, Elem } from "../../../utils/bem"; +import "./InfoBanner.scss"; + +const injector = inject(({ store }) => { + console.log('store', store); + return {}; +}); + +export const InfoBanner = injector(({}) => { + return ( + + + + Looking for automatic labeling? Try it now -> + + + + ); +}); \ No newline at end of file diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.scss b/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.scss new file mode 100644 index 000000000000..e913beb6d5fa --- /dev/null +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.scss @@ -0,0 +1,32 @@ +.info-banner { + background-color: #fff3e0; + border: 1px solid #ffe0b2; + border-radius: 4px; + padding: 5px 12px; + margin: 8px 0; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + + &__content { + display: flex; + align-items: center; + gap: 8px; + color: #795548; + font-size: 14px; + line-height: 1.4; + } + + &__link { + color: #f57c00; + text-decoration: none; + font-weight: 500; + transition: color 0.15s ease; + + &:hover { + color: #ef6c00; + text-decoration: underline; + } + } +} From dd8d1d70afe34a8cfd373f04a429ce491c9f6c3e Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 13 Dec 2024 10:37:44 +0000 Subject: [PATCH 04/30] Revert data import --- label_studio/data_import/functions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/label_studio/data_import/functions.py b/label_studio/data_import/functions.py index 10cabdea3381..d0c4262412a4 100644 --- a/label_studio/data_import/functions.py +++ b/label_studio/data_import/functions.py @@ -19,11 +19,7 @@ def async_import_background( - import_id, - user_id, - recalculate_stats_func: Optional[Callable[..., None]] = None, - callback_func: Optional[Callable[..., None]] = None, - **kwargs + import_id, user_id, recalculate_stats_func: Optional[Callable[..., None]] = None, **kwargs ): with transaction.atomic(): try: From 45c9fd02adae54ef7c259908939c327b53c4fa60 Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 13 Dec 2024 10:38:55 +0000 Subject: [PATCH 05/30] Revert toolbar --- .../datamanager/src/components/DataManager/Toolbar/Toolbar.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/Toolbar.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/Toolbar.jsx index f8f4c6d0b515..c34e5486fe94 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/Toolbar.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/Toolbar.jsx @@ -21,6 +21,7 @@ export const Toolbar = injector( {section.map((instrument, i) => { const Instrument = store.SDK.getInstrument(instrument); + return Instrument ? ( ) : null; From de6cb0a16fe39ce0b4e66974e8b2bb5110088c8f Mon Sep 17 00:00:00 2001 From: nik Date: Mon, 16 Dec 2024 12:39:52 +0000 Subject: [PATCH 06/30] Fix contextlog --- label_studio/core/utils/contextlog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/label_studio/core/utils/contextlog.py b/label_studio/core/utils/contextlog.py index a1042609ab1b..fa9595306f9f 100644 --- a/label_studio/core/utils/contextlog.py +++ b/label_studio/core/utils/contextlog.py @@ -271,7 +271,7 @@ def create_payload(self, request, response, body): else: namespace = request.resolver_match.namespace if request.resolver_match else None status_code = response.status_code - content_type = response.content_type + content_type = response.content_type if hasattr(response, 'content_type') else None response_content = self._get_response_content(response) payload = { From 1995e79dfb745d3ec41fc99c148d5c57810803fb Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 17 Dec 2024 12:52:33 +0000 Subject: [PATCH 07/30] Fix order of project.save() --- label_studio/projects/models.py | 12 ++++++++---- label_studio/users/models.py | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/label_studio/projects/models.py b/label_studio/projects/models.py index 8c0c1c6fbcb1..905922887202 100644 --- a/label_studio/projects/models.py +++ b/label_studio/projects/models.py @@ -731,8 +731,11 @@ def get_overall(name): def save(self, *args, update_fields=None, recalc=True, **kwargs): exists = True if self.pk else False project_with_config_just_created = not exists and self.label_config + + label_config_has_changed = self._label_config_has_changed() + logger.debug(f'Label config has changed: {label_config_has_changed}, original: {self.__original_label_config}, new: {self.label_config}') - if self._label_config_has_changed() or project_with_config_just_created: + if label_config_has_changed or project_with_config_just_created: self.data_types = extract_data_types(self.label_config) self.parsed_label_config = parse_config(self.label_config) self.label_config_hash = hash(str(self.parsed_label_config)) @@ -744,14 +747,15 @@ def save(self, *args, update_fields=None, recalc=True, **kwargs): if update_fields is not None: update_fields = {'control_weights'}.union(update_fields) - if self._label_config_has_changed(): + super(Project, self).save(*args, update_fields=update_fields, **kwargs) + + if label_config_has_changed: + # save the new label config for future comparison self.__original_label_config = self.label_config # if tasks are already imported, emit signal that project is configured and ready for labeling if self.num_tasks > 0: ProjectSignals.post_label_config_and_import_tasks.send(sender=Project, project=self) - super(Project, self).save(*args, update_fields=update_fields, **kwargs) - if not exists: steps = ProjectOnboardingSteps.objects.all() objs = [ProjectOnboarding(project=self, step=step) for step in steps] diff --git a/label_studio/users/models.py b/label_studio/users/models.py index 3ca2420d22b6..9601277c8dd2 100644 --- a/label_studio/users/models.py +++ b/label_studio/users/models.py @@ -16,6 +16,7 @@ from organizations.models import Organization from rest_framework.authtoken.models import Token from users.functions import hash_upload +from users.product_tours.models import UserTour YEAR_START = 1980 YEAR_CHOICES = [] From e683be97757998ef5b333824748ab44490d6ed7d Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 17 Dec 2024 12:53:50 +0000 Subject: [PATCH 08/30] Add UserTour model --- .../users/migrations/0010_usertour.py | 27 +++++++++ label_studio/users/product_tours/models.py | 59 +++++++++++++++++++ .../users/product_tours/serializers.py | 15 +++++ 3 files changed, 101 insertions(+) create mode 100644 label_studio/users/migrations/0010_usertour.py create mode 100644 label_studio/users/product_tours/models.py create mode 100644 label_studio/users/product_tours/serializers.py diff --git a/label_studio/users/migrations/0010_usertour.py b/label_studio/users/migrations/0010_usertour.py new file mode 100644 index 000000000000..003c81a654d5 --- /dev/null +++ b/label_studio/users/migrations/0010_usertour.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.15 on 2024-12-17 12:52 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0009_auto_20231201_0001'), + ] + + operations = [ + migrations.CreateModel( + name='UserTour', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tour_name', models.CharField(help_text='Unique identifier for the product tour', max_length=256)), + ('state', models.CharField(choices=[('completed', 'completed'), ('skipped', 'skipped')], default='completed', help_text='Current state of the tour for this user', max_length=32)), + ('interaction_data', models.JSONField(blank=True, default=dict, help_text='Additional data about user interaction with the tour')), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='When this tour record was created')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='When this tour record was last updated')), + ('user', models.ForeignKey(help_text='User who interacted with the tour', on_delete=django.db.models.deletion.CASCADE, related_name='tours', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/label_studio/users/product_tours/models.py b/label_studio/users/product_tours/models.py new file mode 100644 index 000000000000..31bd3191e416 --- /dev/null +++ b/label_studio/users/product_tours/models.py @@ -0,0 +1,59 @@ +import datetime +from django.db import models +from enum import Enum +from typing import Optional, Dict, Any +from pydantic import BaseModel, Field + + +class TourState(str, Enum): + COMPLETED = "completed" + SKIPPED = "skipped" + + +class TourInteractionData(BaseModel): + """Pydantic model for validating tour interaction data""" + skipped_at_step: Optional[int] = Field(None, description="Step number where tour was skipped") + last_viewed_step: Optional[int] = Field(None, description="Last step number viewed") + completion_date: Optional[datetime.datetime] = Field(None, description="When the tour was completed") + additional_data: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Extensible field for additional interaction data") + + +class UserTour(models.Model): + """Stores product tour state and interaction data for users""" + user = models.ForeignKey( + 'User', + on_delete=models.CASCADE, + related_name='tours', + help_text='User who interacted with the tour' + ) + + tour_name = models.CharField( + max_length=256, + help_text='Unique identifier for the product tour' + ) + + state = models.CharField( + max_length=32, + choices=[(state.value, state.value) for state in TourState], + default=TourState.COMPLETED.value, + help_text='Current state of the tour for this user' + ) + + interaction_data = models.JSONField( + default=dict, + blank=True, + help_text='Additional data about user interaction with the tour' + ) + + created_at = models.DateTimeField( + auto_now_add=True, + help_text='When this tour record was created' + ) + + updated_at = models.DateTimeField( + auto_now=True, + help_text='When this tour record was last updated' + ) + + def __str__(self): + return f"{self.user.email} - {self.tour_name} ({self.state})" diff --git a/label_studio/users/product_tours/serializers.py b/label_studio/users/product_tours/serializers.py new file mode 100644 index 000000000000..76186a2d0a99 --- /dev/null +++ b/label_studio/users/product_tours/serializers.py @@ -0,0 +1,15 @@ +from rest_framework import serializers +from .models import UserTour, TourInteractionData + +class UserTourSerializer(serializers.ModelSerializer): + class Meta: + model = UserTour + fields = '__all__' + + def validate_interaction_data(self, value): + try: + # Validate interaction data using pydantic model + TourInteractionData(**value) + return value + except Exception as e: + raise serializers.ValidationError(f"Invalid interaction data format: {str(e)}") From ed93c9f4c21160a3f219316080cbe5bb8f3ecf30 Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 17 Dec 2024 13:02:03 +0000 Subject: [PATCH 09/30] Update states --- label_studio/users/migrations/0010_usertour.py | 4 ++-- label_studio/users/product_tours/models.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/label_studio/users/migrations/0010_usertour.py b/label_studio/users/migrations/0010_usertour.py index 003c81a654d5..21f75a2a7f6d 100644 --- a/label_studio/users/migrations/0010_usertour.py +++ b/label_studio/users/migrations/0010_usertour.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.15 on 2024-12-17 12:52 +# Generated by Django 4.2.15 on 2024-12-17 13:01 from django.conf import settings from django.db import migrations, models @@ -17,7 +17,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('tour_name', models.CharField(help_text='Unique identifier for the product tour', max_length=256)), - ('state', models.CharField(choices=[('completed', 'completed'), ('skipped', 'skipped')], default='completed', help_text='Current state of the tour for this user', max_length=32)), + ('state', models.CharField(choices=[('ready', 'ready'), ('completed', 'completed'), ('skipped', 'skipped')], default='ready', help_text='Current state of the tour for this user: "ready" when tour is initiated, "completed" when user finishes the tour, "skipped" when user cancels the tour.', max_length=32)), ('interaction_data', models.JSONField(blank=True, default=dict, help_text='Additional data about user interaction with the tour')), ('created_at', models.DateTimeField(auto_now_add=True, help_text='When this tour record was created')), ('updated_at', models.DateTimeField(auto_now=True, help_text='When this tour record was last updated')), diff --git a/label_studio/users/product_tours/models.py b/label_studio/users/product_tours/models.py index 31bd3191e416..9276f71c39c0 100644 --- a/label_studio/users/product_tours/models.py +++ b/label_studio/users/product_tours/models.py @@ -6,6 +6,7 @@ class TourState(str, Enum): + READY = "ready" COMPLETED = "completed" SKIPPED = "skipped" @@ -35,8 +36,8 @@ class UserTour(models.Model): state = models.CharField( max_length=32, choices=[(state.value, state.value) for state in TourState], - default=TourState.COMPLETED.value, - help_text='Current state of the tour for this user' + default=TourState.READY.value, + help_text='Current state of the tour for this user: "ready" when tour is initiated, "completed" when user finishes the tour, "skipped" when user cancels the tour.' ) interaction_data = models.JSONField( From 4b54b0989874b4d0ca2796441156eb4cbec930d8 Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 17 Dec 2024 22:16:12 +0000 Subject: [PATCH 10/30] update data model, add url --- label_studio/ml_models/models.py | 7 +++ label_studio/users/api.py | 1 + ...10_usertour.py => 0010_userproducttour.py} | 6 +-- label_studio/users/models.py | 2 +- label_studio/users/product_tours/api.py | 48 +++++++++++++++++++ .../product_tours/configs/create_prompt.yml | 25 ++++++++++ .../product_tours/configs/prompts_page.yml | 9 ++++ .../configs/show_autolabel_button.yml | 6 +++ label_studio/users/product_tours/models.py | 23 ++++----- .../users/product_tours/serializers.py | 36 ++++++++++++-- label_studio/users/urls.py | 2 + 11 files changed, 146 insertions(+), 19 deletions(-) rename label_studio/users/migrations/{0010_usertour.py => 0010_userproducttour.py} (85%) create mode 100644 label_studio/users/product_tours/api.py create mode 100644 label_studio/users/product_tours/configs/create_prompt.yml create mode 100644 label_studio/users/product_tours/configs/prompts_page.yml create mode 100644 label_studio/users/product_tours/configs/show_autolabel_button.yml diff --git a/label_studio/ml_models/models.py b/label_studio/ml_models/models.py index f40218dd9566..e259f59a1e57 100644 --- a/label_studio/ml_models/models.py +++ b/label_studio/ml_models/models.py @@ -114,6 +114,13 @@ class ThirdPartyModelVersion(ModelVersion): organization = models.ForeignKey( 'organizations.Organization', on_delete=models.CASCADE, related_name='third_party_model_versions', null=True ) + + @property + def project(self): + # TODO: can it be just a property of the model version? + if self.parent_model and self.parent_model.associated_projects.exists(): + return self.parent_model.associated_projects.first() + return None def has_permission(self, user): return user.active_organization == self.organization diff --git a/label_studio/users/api.py b/label_studio/users/api.py index 7520ebadf0b0..3bfedf2662ac 100644 --- a/label_studio/users/api.py +++ b/label_studio/users/api.py @@ -17,6 +17,7 @@ from users.functions import check_avatar from users.models import User from users.serializers import UserSerializer, UserSerializerUpdate +from users.product_tours.api import ProductTourAPI logger = logging.getLogger(__name__) diff --git a/label_studio/users/migrations/0010_usertour.py b/label_studio/users/migrations/0010_userproducttour.py similarity index 85% rename from label_studio/users/migrations/0010_usertour.py rename to label_studio/users/migrations/0010_userproducttour.py index 21f75a2a7f6d..ffe9395c64f7 100644 --- a/label_studio/users/migrations/0010_usertour.py +++ b/label_studio/users/migrations/0010_userproducttour.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.15 on 2024-12-17 13:01 +# Generated by Django 4.2.15 on 2024-12-17 18:56 from django.conf import settings from django.db import migrations, models @@ -13,10 +13,10 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='UserTour', + name='UserProductTour', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('tour_name', models.CharField(help_text='Unique identifier for the product tour', max_length=256)), + ('name', models.CharField(help_text='Unique identifier for the product tour. Name must match the config name.', max_length=256)), ('state', models.CharField(choices=[('ready', 'ready'), ('completed', 'completed'), ('skipped', 'skipped')], default='ready', help_text='Current state of the tour for this user: "ready" when tour is initiated, "completed" when user finishes the tour, "skipped" when user cancels the tour.', max_length=32)), ('interaction_data', models.JSONField(blank=True, default=dict, help_text='Additional data about user interaction with the tour')), ('created_at', models.DateTimeField(auto_now_add=True, help_text='When this tour record was created')), diff --git a/label_studio/users/models.py b/label_studio/users/models.py index 9601277c8dd2..76eb165cf842 100644 --- a/label_studio/users/models.py +++ b/label_studio/users/models.py @@ -16,7 +16,7 @@ from organizations.models import Organization from rest_framework.authtoken.models import Token from users.functions import hash_upload -from users.product_tours.models import UserTour +from users.product_tours.models import UserProductTour YEAR_START = 1980 YEAR_CHOICES = [] diff --git a/label_studio/users/product_tours/api.py b/label_studio/users/product_tours/api.py new file mode 100644 index 000000000000..e29d08c4e743 --- /dev/null +++ b/label_studio/users/product_tours/api.py @@ -0,0 +1,48 @@ +import pathlib +import yaml +import logging + +from rest_framework import generics, status +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from rest_framework.exceptions import ValidationError +from rest_framework.decorators import api_view, permission_classes +from users.product_tours.models import UserProductTour +from functools import cached_property +from drf_yasg.utils import swagger_auto_schema +from .serializers import UserProductTourSerializer + +logger = logging.getLogger(__name__) + + +class ProductTourAPI(generics.RetrieveUpdateAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = UserProductTourSerializer + + def get_tour_name(self): + name = self.request.query_params.get('name') + if not name: + raise ValidationError('Name is required') + # normalize name for subsequent checks + return name.replace('-', '_').lower() + + def get_serializer_context(self): + context = super().get_serializer_context() + context['name'] = self.get_tour_name() + return context + + def get_object(self): + name = self.get_tour_name() + + # TODO: add additional checks, e.g. user agent, role, etc. + + tour = UserProductTour.objects.filter(user=self.request.user, name=name).first() + if not tour: + logger.info(f'Product tour {name} not found for user {self.request.user.id}. Creating new tour.') + tour = self.get_serializer(data={'user': self.request.user.id, 'name': name}) + tour.is_valid(raise_exception=True) + tour.save() + else: + logger.info(f'Product tour {name} requested for user {self.request.user.id}.') + + return tour diff --git a/label_studio/users/product_tours/configs/create_prompt.yml b/label_studio/users/product_tours/configs/create_prompt.yml new file mode 100644 index 000000000000..df8a4bbd8aa9 --- /dev/null +++ b/label_studio/users/product_tours/configs/create_prompt.yml @@ -0,0 +1,25 @@ +steps: +- target: body + placement: center + title: '
Scale Your Expertise with Prompts
' + content: '
Achieve high accuracy and consistency labeling large datasets with the power of LLMs.

Let us show you how.
' + +- target: .cm-editor + placement: right + title: '
Step 1 of 4
' + content: '
Type in your instructions using natural language in this text box. You may include variables names to refer to specific pieces of your data.
' + +- target: .lsf-data-table + placement: left + title: '
Step 2 of 4
' + content: 'On the right side, you can view the data to be labeled. This is where your predictions will be displayed.' + +- target: '[data-testid="save-model-version"]' + placement: bottom + title: '
Step 3 of 4
' + content: '
When you are satisfied with your prompt, press Save to ensure this version of your prompt is safely stored.
' + +- target: '[data-testid="evaluate-model-button"]' + placement: top + title: '
🎉
' + content: '
That''s it! After saving, just click on Evaluate to start getting predictions for your tasks.
' \ No newline at end of file diff --git a/label_studio/users/product_tours/configs/prompts_page.yml b/label_studio/users/product_tours/configs/prompts_page.yml new file mode 100644 index 000000000000..dd44d8994e42 --- /dev/null +++ b/label_studio/users/product_tours/configs/prompts_page.yml @@ -0,0 +1,9 @@ +steps: + - target: body + placement: center + title: '
Welcome to Prompts!
' + content: '
Set up Prompts to help you rapidly pre-label projects.
' + + - target: ".lsf-data-table__body-row[data-index='0'] .lsf-models-list__model-name" + content: '
Click on this sample Prompt to get you started.
' + isFixed: true diff --git a/label_studio/users/product_tours/configs/show_autolabel_button.yml b/label_studio/users/product_tours/configs/show_autolabel_button.yml new file mode 100644 index 000000000000..261e34171d8a --- /dev/null +++ b/label_studio/users/product_tours/configs/show_autolabel_button.yml @@ -0,0 +1,6 @@ +steps: + - target: .lsf-auto-labeling-button + placement: bottom + title: 'Scale Your Expertise with Prompts' + content: 'Harness the power of LLMs to automatically label your data with unmatched accuracy and consistency.' + disableBeacon: true diff --git a/label_studio/users/product_tours/models.py b/label_studio/users/product_tours/models.py index 9276f71c39c0..2def052acf37 100644 --- a/label_studio/users/product_tours/models.py +++ b/label_studio/users/product_tours/models.py @@ -5,21 +5,22 @@ from pydantic import BaseModel, Field -class TourState(str, Enum): +class ProductTourState(str, Enum): READY = "ready" COMPLETED = "completed" SKIPPED = "skipped" -class TourInteractionData(BaseModel): +class ProductTourInteractionData(BaseModel): """Pydantic model for validating tour interaction data""" - skipped_at_step: Optional[int] = Field(None, description="Step number where tour was skipped") - last_viewed_step: Optional[int] = Field(None, description="Last step number viewed") - completion_date: Optional[datetime.datetime] = Field(None, description="When the tour was completed") + index: Optional[int] = Field(None, description="Step number where tour was completed") + action: Optional[str] = Field(None, description="Action taken during the tour") + type: Optional[str] = Field(None, description="Type of interaction") + status: Optional[str] = Field(None, description="Status of the interaction") additional_data: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Extensible field for additional interaction data") -class UserTour(models.Model): +class UserProductTour(models.Model): """Stores product tour state and interaction data for users""" user = models.ForeignKey( 'User', @@ -28,15 +29,15 @@ class UserTour(models.Model): help_text='User who interacted with the tour' ) - tour_name = models.CharField( + name = models.CharField( max_length=256, - help_text='Unique identifier for the product tour' + help_text='Unique identifier for the product tour. Name must match the config name.' ) state = models.CharField( max_length=32, - choices=[(state.value, state.value) for state in TourState], - default=TourState.READY.value, + choices=[(state.value, state.value) for state in ProductTourState], + default=ProductTourState.READY.value, help_text='Current state of the tour for this user: "ready" when tour is initiated, "completed" when user finishes the tour, "skipped" when user cancels the tour.' ) @@ -57,4 +58,4 @@ class UserTour(models.Model): ) def __str__(self): - return f"{self.user.email} - {self.tour_name} ({self.state})" + return f"{self.user.email} - {self.name} ({self.state})" diff --git a/label_studio/users/product_tours/serializers.py b/label_studio/users/product_tours/serializers.py index 76186a2d0a99..473e643ff878 100644 --- a/label_studio/users/product_tours/serializers.py +++ b/label_studio/users/product_tours/serializers.py @@ -1,15 +1,43 @@ +import pathlib +import yaml from rest_framework import serializers -from .models import UserTour, TourInteractionData +from functools import cached_property +from .models import UserProductTour, ProductTourInteractionData -class UserTourSerializer(serializers.ModelSerializer): +PRODUCT_TOURS_CONFIGS_DIR = pathlib.Path(__file__).parent / 'configs' + +class UserProductTourSerializer(serializers.ModelSerializer): + steps = serializers.SerializerMethodField(read_only=True) + class Meta: - model = UserTour + model = UserProductTour fields = '__all__' + @cached_property + def available_tours(self): + return {pathlib.Path(f).stem for f in PRODUCT_TOURS_CONFIGS_DIR.iterdir()} + + def validate_name(self, value): + + if value not in self.available_tours: + raise serializers.ValidationError(f'Product tour {value} not found. Available tours: {self.available_tours}') + + return value + + def load_tour_config(self): + # TODO: get product tour from yaml file. Later we move it to remote storage, e.g. S3 + filepath = PRODUCT_TOURS_CONFIGS_DIR / f'{self.context["name"]}.yml' + with open(filepath, 'r') as f: + return yaml.safe_load(f) + + def get_steps(self, obj): + config = self.load_tour_config() + return config.get('steps', []) + def validate_interaction_data(self, value): try: # Validate interaction data using pydantic model - TourInteractionData(**value) + ProductTourInteractionData(**value) return value except Exception as e: raise serializers.ValidationError(f"Invalid interaction data format: {str(e)}") diff --git a/label_studio/users/urls.py b/label_studio/users/urls.py index e5110aa626ee..258e9cfecf72 100644 --- a/label_studio/users/urls.py +++ b/label_studio/users/urls.py @@ -23,6 +23,8 @@ path('api/current-user/reset-token/', api.UserResetTokenAPI.as_view(), name='current-user-reset-token'), path('api/current-user/token', api.UserGetTokenAPI.as_view(), name='current-user-token'), path('api/current-user/whoami', api.UserWhoAmIAPI.as_view(), name='current-user-whoami'), + # Product tours + path('api/current-user/product-tour', api.ProductTourAPI.as_view(), name='product-tour'), ] # When CLOUD_FILE_STORAGE_ENABLED is set, avatars are uploaded to cloud storage with a different URL pattern. From 842c951e881e9b3d9ccfa824f0a0b0e7fd3a6fe7 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 18 Dec 2024 13:39:56 +0000 Subject: [PATCH 11/30] Optimize /api/templates --- label_studio/projects/api.py | 38 +++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/label_studio/projects/api.py b/label_studio/projects/api.py index 48f76bbf3083..91a44daf359f 100644 --- a/label_studio/projects/api.py +++ b/label_studio/projects/api.py @@ -786,30 +786,36 @@ def perform_create(self, serializer): self.request.user.active_organization, project, WebhookAction.TASKS_CREATED, [instance] ) return instance + + +def read_templates_and_groups(): + annotation_templates_dir = find_dir('annotation_templates') + configs = [] + for config_file in pathlib.Path(annotation_templates_dir).glob('**/*.yml'): + config = read_yaml(config_file) + if settings.VERSION_EDITION == 'Community': + if settings.VERSION_EDITION.lower() != config.get('type', 'community'): + continue + if config.get('image', '').startswith('/static') and settings.HOSTNAME: + # if hostname set manually, create full image urls + config['image'] = settings.HOSTNAME + config['image'] + configs.append(config) + template_groups_file = find_file(os.path.join('annotation_templates', 'groups.txt')) + with open(template_groups_file, encoding='utf-8') as f: + groups = f.read().splitlines() + logger.debug(f'{len(configs)} templates found.') + return {'templates': configs, 'groups': groups} class TemplateListAPI(generics.ListAPIView): parser_classes = (JSONParser, FormParser, MultiPartParser) permission_required = all_permissions.projects_view swagger_schema = None + # load this once in memory for performance + templates_and_groups = read_templates_and_groups() def list(self, request, *args, **kwargs): - annotation_templates_dir = find_dir('annotation_templates') - configs = [] - for config_file in pathlib.Path(annotation_templates_dir).glob('**/*.yml'): - config = read_yaml(config_file) - if settings.VERSION_EDITION == 'Community': - if settings.VERSION_EDITION.lower() != config.get('type', 'community'): - continue - if config.get('image', '').startswith('/static') and settings.HOSTNAME: - # if hostname set manually, create full image urls - config['image'] = settings.HOSTNAME + config['image'] - configs.append(config) - template_groups_file = find_file(os.path.join('annotation_templates', 'groups.txt')) - with open(template_groups_file, encoding='utf-8') as f: - groups = f.read().splitlines() - logger.debug(f'{len(configs)} templates found.') - return Response({'templates': configs, 'groups': groups}) + return Response(self.templates_and_groups) class ProjectSampleTask(generics.RetrieveAPIView): From 8e2e90ba680ba438e0c8ccaf75ea70d790f1e72e Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 18 Dec 2024 15:29:56 +0000 Subject: [PATCH 12/30] Fix tests --- label_studio/ml_models/models.py | 2 +- label_studio/projects/api.py | 4 +- label_studio/projects/models.py | 11 ++-- label_studio/projects/signals.py | 5 +- label_studio/tasks/serializers.py | 4 +- label_studio/users/api.py | 1 - label_studio/users/models.py | 1 - label_studio/users/product_tours/api.py | 21 +++---- label_studio/users/product_tours/models.py | 56 ++++++++----------- .../users/product_tours/serializers.py | 29 ++++++---- 10 files changed, 64 insertions(+), 70 deletions(-) diff --git a/label_studio/ml_models/models.py b/label_studio/ml_models/models.py index e259f59a1e57..bd3e7c605966 100644 --- a/label_studio/ml_models/models.py +++ b/label_studio/ml_models/models.py @@ -114,7 +114,7 @@ class ThirdPartyModelVersion(ModelVersion): organization = models.ForeignKey( 'organizations.Organization', on_delete=models.CASCADE, related_name='third_party_model_versions', null=True ) - + @property def project(self): # TODO: can it be just a property of the model version? diff --git a/label_studio/projects/api.py b/label_studio/projects/api.py index 91a44daf359f..ba7630b1877c 100644 --- a/label_studio/projects/api.py +++ b/label_studio/projects/api.py @@ -786,8 +786,8 @@ def perform_create(self, serializer): self.request.user.active_organization, project, WebhookAction.TASKS_CREATED, [instance] ) return instance - - + + def read_templates_and_groups(): annotation_templates_dir = find_dir('annotation_templates') configs = [] diff --git a/label_studio/projects/models.py b/label_studio/projects/models.py index 905922887202..4a4cd6442fa6 100644 --- a/label_studio/projects/models.py +++ b/label_studio/projects/models.py @@ -3,6 +3,7 @@ import json import logging from typing import Any, Mapping, Optional + from annoying.fields import AutoOneToOneField from core.feature_flags import flag_set from core.label_config import ( @@ -658,7 +659,7 @@ def display_count(count: int, type: str) -> Optional[str]: def _label_config_has_changed(self): return self.label_config != self.__original_label_config - + @property def label_config_is_not_default(self): return self.label_config != Project._meta.get_field('label_config').default @@ -731,9 +732,11 @@ def get_overall(name): def save(self, *args, update_fields=None, recalc=True, **kwargs): exists = True if self.pk else False project_with_config_just_created = not exists and self.label_config - + label_config_has_changed = self._label_config_has_changed() - logger.debug(f'Label config has changed: {label_config_has_changed}, original: {self.__original_label_config}, new: {self.label_config}') + logger.debug( + f'Label config has changed: {label_config_has_changed}, original: {self.__original_label_config}, new: {self.label_config}' + ) if label_config_has_changed or project_with_config_just_created: self.data_types = extract_data_types(self.label_config) @@ -748,7 +751,7 @@ def save(self, *args, update_fields=None, recalc=True, **kwargs): update_fields = {'control_weights'}.union(update_fields) super(Project, self).save(*args, update_fields=update_fields, **kwargs) - + if label_config_has_changed: # save the new label config for future comparison self.__original_label_config = self.label_config diff --git a/label_studio/projects/signals.py b/label_studio/projects/signals.py index 8f36230565b4..4e1b7a2256f3 100644 --- a/label_studio/projects/signals.py +++ b/label_studio/projects/signals.py @@ -5,13 +5,14 @@ class ProjectSignals: """ Signals for project: implements observer pattern for custom signals. Example: - + # publisher ProjectSignals.my_signal.send(sender=self, project=project) - + # observer @receiver(ProjectSignals.my_signal) def my_observer(sender, **kwargs): ... """ + post_label_config_and_import_tasks = Signal() diff --git a/label_studio/tasks/serializers.py b/label_studio/tasks/serializers.py index 4a32e92bc849..be30ac963abe 100644 --- a/label_studio/tasks/serializers.py +++ b/label_studio/tasks/serializers.py @@ -586,9 +586,9 @@ def post_process_tasks(user, db_tasks): @staticmethod def add_annotation_fields(body, user, action): return body - + @staticmethod - def post_process_custom_callback(project_id): + def post_process_custom_callback(project_id, user): pass class Meta: diff --git a/label_studio/users/api.py b/label_studio/users/api.py index 3bfedf2662ac..7520ebadf0b0 100644 --- a/label_studio/users/api.py +++ b/label_studio/users/api.py @@ -17,7 +17,6 @@ from users.functions import check_avatar from users.models import User from users.serializers import UserSerializer, UserSerializerUpdate -from users.product_tours.api import ProductTourAPI logger = logging.getLogger(__name__) diff --git a/label_studio/users/models.py b/label_studio/users/models.py index 76eb165cf842..3ca2420d22b6 100644 --- a/label_studio/users/models.py +++ b/label_studio/users/models.py @@ -16,7 +16,6 @@ from organizations.models import Organization from rest_framework.authtoken.models import Token from users.functions import hash_upload -from users.product_tours.models import UserProductTour YEAR_START = 1980 YEAR_CHOICES = [] diff --git a/label_studio/users/product_tours/api.py b/label_studio/users/product_tours/api.py index e29d08c4e743..06de973d5aae 100644 --- a/label_studio/users/product_tours/api.py +++ b/label_studio/users/product_tours/api.py @@ -1,15 +1,10 @@ -import pathlib -import yaml import logging -from rest_framework import generics, status -from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated +from rest_framework import generics from rest_framework.exceptions import ValidationError -from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated from users.product_tours.models import UserProductTour -from functools import cached_property -from drf_yasg.utils import swagger_auto_schema + from .serializers import UserProductTourSerializer logger = logging.getLogger(__name__) @@ -18,22 +13,22 @@ class ProductTourAPI(generics.RetrieveUpdateAPIView): permission_classes = (IsAuthenticated,) serializer_class = UserProductTourSerializer - + def get_tour_name(self): name = self.request.query_params.get('name') if not name: raise ValidationError('Name is required') # normalize name for subsequent checks return name.replace('-', '_').lower() - + def get_serializer_context(self): context = super().get_serializer_context() context['name'] = self.get_tour_name() return context - + def get_object(self): name = self.get_tour_name() - + # TODO: add additional checks, e.g. user agent, role, etc. tour = UserProductTour.objects.filter(user=self.request.user, name=name).first() @@ -44,5 +39,5 @@ def get_object(self): tour.save() else: logger.info(f'Product tour {name} requested for user {self.request.user.id}.') - + return tour diff --git a/label_studio/users/product_tours/models.py b/label_studio/users/product_tours/models.py index 2def052acf37..d196eb353178 100644 --- a/label_studio/users/product_tours/models.py +++ b/label_studio/users/product_tours/models.py @@ -1,61 +1,53 @@ -import datetime -from django.db import models from enum import Enum -from typing import Optional, Dict, Any +from typing import Any, Dict, Optional + +from django.db import models from pydantic import BaseModel, Field class ProductTourState(str, Enum): - READY = "ready" - COMPLETED = "completed" - SKIPPED = "skipped" + READY = 'ready' + COMPLETED = 'completed' + SKIPPED = 'skipped' class ProductTourInteractionData(BaseModel): """Pydantic model for validating tour interaction data""" - index: Optional[int] = Field(None, description="Step number where tour was completed") - action: Optional[str] = Field(None, description="Action taken during the tour") - type: Optional[str] = Field(None, description="Type of interaction") - status: Optional[str] = Field(None, description="Status of the interaction") - additional_data: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Extensible field for additional interaction data") + + index: Optional[int] = Field(None, description='Step number where tour was completed') + action: Optional[str] = Field(None, description='Action taken during the tour') + type: Optional[str] = Field(None, description='Type of interaction') + status: Optional[str] = Field(None, description='Status of the interaction') + additional_data: Optional[Dict[str, Any]] = Field( + default_factory=dict, description='Extensible field for additional interaction data' + ) class UserProductTour(models.Model): """Stores product tour state and interaction data for users""" + user = models.ForeignKey( - 'User', - on_delete=models.CASCADE, - related_name='tours', - help_text='User who interacted with the tour' + 'User', on_delete=models.CASCADE, related_name='tours', help_text='User who interacted with the tour' ) - + name = models.CharField( - max_length=256, - help_text='Unique identifier for the product tour. Name must match the config name.' + max_length=256, help_text='Unique identifier for the product tour. Name must match the config name.' ) - + state = models.CharField( max_length=32, choices=[(state.value, state.value) for state in ProductTourState], default=ProductTourState.READY.value, - help_text='Current state of the tour for this user: "ready" when tour is initiated, "completed" when user finishes the tour, "skipped" when user cancels the tour.' + help_text='Current state of the tour for this user: "ready" when tour is initiated, "completed" when user finishes the tour, "skipped" when user cancels the tour.', ) interaction_data = models.JSONField( - default=dict, - blank=True, - help_text='Additional data about user interaction with the tour' + default=dict, blank=True, help_text='Additional data about user interaction with the tour' ) - created_at = models.DateTimeField( - auto_now_add=True, - help_text='When this tour record was created' - ) + created_at = models.DateTimeField(auto_now_add=True, help_text='When this tour record was created') - updated_at = models.DateTimeField( - auto_now=True, - help_text='When this tour record was last updated' - ) + updated_at = models.DateTimeField(auto_now=True, help_text='When this tour record was last updated') def __str__(self): - return f"{self.user.email} - {self.name} ({self.state})" + return f'{self.user.email} - {self.name} ({self.state})' diff --git a/label_studio/users/product_tours/serializers.py b/label_studio/users/product_tours/serializers.py index 473e643ff878..a5500178e9fe 100644 --- a/label_studio/users/product_tours/serializers.py +++ b/label_studio/users/product_tours/serializers.py @@ -1,43 +1,48 @@ import pathlib +from functools import cached_property + import yaml from rest_framework import serializers -from functools import cached_property -from .models import UserProductTour, ProductTourInteractionData + +from .models import ProductTourInteractionData, UserProductTour PRODUCT_TOURS_CONFIGS_DIR = pathlib.Path(__file__).parent / 'configs' + class UserProductTourSerializer(serializers.ModelSerializer): steps = serializers.SerializerMethodField(read_only=True) - + class Meta: model = UserProductTour fields = '__all__' - + @cached_property def available_tours(self): return {pathlib.Path(f).stem for f in PRODUCT_TOURS_CONFIGS_DIR.iterdir()} - + def validate_name(self, value): - + if value not in self.available_tours: - raise serializers.ValidationError(f'Product tour {value} not found. Available tours: {self.available_tours}') - + raise serializers.ValidationError( + f'Product tour {value} not found. Available tours: {self.available_tours}' + ) + return value - + def load_tour_config(self): # TODO: get product tour from yaml file. Later we move it to remote storage, e.g. S3 filepath = PRODUCT_TOURS_CONFIGS_DIR / f'{self.context["name"]}.yml' with open(filepath, 'r') as f: return yaml.safe_load(f) - + def get_steps(self, obj): config = self.load_tour_config() return config.get('steps', []) - + def validate_interaction_data(self, value): try: # Validate interaction data using pydantic model ProductTourInteractionData(**value) return value except Exception as e: - raise serializers.ValidationError(f"Invalid interaction data format: {str(e)}") + raise serializers.ValidationError(f'Invalid interaction data format: {str(e)}') From 6e21afb3accddd6322977ae1c16df6458440f212 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 18 Dec 2024 15:35:44 +0000 Subject: [PATCH 13/30] Fix url --- label_studio/users/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/label_studio/users/urls.py b/label_studio/users/urls.py index 258e9cfecf72..34325eedc0eb 100644 --- a/label_studio/users/urls.py +++ b/label_studio/users/urls.py @@ -8,6 +8,7 @@ from django.views.static import serve from rest_framework import routers from users import api, views +from users.product_tours import api as product_tours_api router = routers.DefaultRouter() router.register(r'users', api.UserAPI, basename='user') @@ -24,7 +25,7 @@ path('api/current-user/token', api.UserGetTokenAPI.as_view(), name='current-user-token'), path('api/current-user/whoami', api.UserWhoAmIAPI.as_view(), name='current-user-whoami'), # Product tours - path('api/current-user/product-tour', api.ProductTourAPI.as_view(), name='product-tour'), + path('api/current-user/product-tour', product_tours_api.ProductTourAPI.as_view(), name='product-tour'), ] # When CLOUD_FILE_STORAGE_ENABLED is set, avatars are uploaded to cloud storage with a different URL pattern. From 1160bc0abd8c20b8cf91cedf7517ebad55d57948 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 18 Dec 2024 16:01:32 +0000 Subject: [PATCH 14/30] Fix tests --- label_studio/core/all_urls.json | 6 ++++++ label_studio/users/product_tours/api.py | 1 + 2 files changed, 7 insertions(+) diff --git a/label_studio/core/all_urls.json b/label_studio/core/all_urls.json index b9f3d2247bc3..308c95ae427f 100644 --- a/label_studio/core/all_urls.json +++ b/label_studio/core/all_urls.json @@ -527,6 +527,12 @@ "name": "current-user-whoami", "decorators": "" }, + { + "url": "/api/current-user/product-tour", + "module": "users.product_tours.api.ProductTourAPI", + "name": "product-tour", + "decorators": "" + }, { "url": "/data/avatars/", "module": "django.views.static.serve", diff --git a/label_studio/users/product_tours/api.py b/label_studio/users/product_tours/api.py index 06de973d5aae..beacb9e5b2d7 100644 --- a/label_studio/users/product_tours/api.py +++ b/label_studio/users/product_tours/api.py @@ -13,6 +13,7 @@ class ProductTourAPI(generics.RetrieveUpdateAPIView): permission_classes = (IsAuthenticated,) serializer_class = UserProductTourSerializer + swagger_schema = None def get_tour_name(self): name = self.request.query_params.get('name') From 0a38941ba5d15327a23ceeab87505eafef7a9199 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 18 Dec 2024 16:05:14 +0000 Subject: [PATCH 15/30] Remove infobanner --- .../DataManager/Toolbar/InfoBanner.jsx | 20 ------------ .../DataManager/Toolbar/InfoBanner.scss | 32 ------------------- .../DataManager/Toolbar/instruments.jsx | 4 --- 3 files changed, 56 deletions(-) delete mode 100644 web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.jsx delete mode 100644 web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.scss diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.jsx deleted file mode 100644 index 91eb34530f50..000000000000 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { inject } from "mobx-react"; -import { Block, Elem } from "../../../utils/bem"; -import "./InfoBanner.scss"; - -const injector = inject(({ store }) => { - console.log('store', store); - return {}; -}); - -export const InfoBanner = injector(({}) => { - return ( - - - - Looking for automatic labeling? Try it now -> - - - - ); -}); \ No newline at end of file diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.scss b/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.scss deleted file mode 100644 index e913beb6d5fa..000000000000 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/InfoBanner.scss +++ /dev/null @@ -1,32 +0,0 @@ -.info-banner { - background-color: #fff3e0; - border: 1px solid #ffe0b2; - border-radius: 4px; - padding: 5px 12px; - margin: 8px 0; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - - &__content { - display: flex; - align-items: center; - gap: 8px; - color: #795548; - font-size: 14px; - line-height: 1.4; - } - - &__link { - color: #f57c00; - text-decoration: none; - font-weight: 500; - transition: color 0.15s ease; - - &:hover { - color: #ef6c00; - text-decoration: underline; - } - } -} diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/instruments.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/instruments.jsx index 7f395a317b54..076c7ffcb333 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/instruments.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/instruments.jsx @@ -15,7 +15,6 @@ import { LoadingPossum } from "./LoadingPossum"; import { OrderButton } from "./OrderButton"; import { RefreshButton } from "./RefreshButton"; import { ViewToggle } from "./ViewToggle"; -import { InfoBanner } from "./InfoBanner"; const style = { minWidth: "110px", @@ -119,9 +118,6 @@ export const instruments = { "error-box": () => { return ; }, - "info-banner": () => { - return ; - }, "import-button": ({ size }) => { return ( From 9d7a403d222fdc001837b486e2469bd2eae3b4d4 Mon Sep 17 00:00:00 2001 From: nik Date: Thu, 19 Dec 2024 10:24:37 +0000 Subject: [PATCH 16/30] Update content --- .../product_tours/configs/create_prompt.yml | 30 +++++++++---------- .../configs/show_autolabel_button.yml | 4 +-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/label_studio/users/product_tours/configs/create_prompt.yml b/label_studio/users/product_tours/configs/create_prompt.yml index df8a4bbd8aa9..38663140b1d3 100644 --- a/label_studio/users/product_tours/configs/create_prompt.yml +++ b/label_studio/users/product_tours/configs/create_prompt.yml @@ -1,25 +1,23 @@ steps: - target: body placement: center - title: '
Scale Your Expertise with Prompts
' - content: '
Achieve high accuracy and consistency labeling large datasets with the power of LLMs.

Let us show you how.
' + title: '
Auto-Label with AI
' + content: '
We’ve set up an initial prompt and supplied some OpenAI credits to help you get started with auto-labeling your project.

Let us show you around!
' - target: .cm-editor placement: right - title: '
Step 1 of 4
' - content: '
Type in your instructions using natural language in this text box. You may include variables names to refer to specific pieces of your data.
' - -- target: .lsf-data-table - placement: left - title: '
Step 2 of 4
' - content: 'On the right side, you can view the data to be labeled. This is where your predictions will be displayed.' - -- target: '[data-testid="save-model-version"]' - placement: bottom - title: '
Step 3 of 4
' - content: '
When you are satisfied with your prompt, press Save to ensure this version of your prompt is safely stored.
' + title: '
Step 1 of 3
' + content: '
We’ve gone ahead and generated a prompt for you based on your project’s labeling configuration.

Feel free to adjust it!
' - target: '[data-testid="evaluate-model-button"]' placement: top - title: '
🎉
' - content: '
That''s it! After saving, just click on Evaluate to start getting predictions for your tasks.
' \ No newline at end of file + title: '
Step 2 of 3
' + content: '
That''s it! After saving, just click on Evaluate to start getting predictions for your tasks.
' + +- target: body + placement: center + title: '
🎉
That’s it!
' + content: 'Now you may convert (or correct) the predictions to annotations by opening the tasks on your project.' + locale: + last: 'Go to Project' + diff --git a/label_studio/users/product_tours/configs/show_autolabel_button.yml b/label_studio/users/product_tours/configs/show_autolabel_button.yml index 261e34171d8a..6a7506bf8539 100644 --- a/label_studio/users/product_tours/configs/show_autolabel_button.yml +++ b/label_studio/users/product_tours/configs/show_autolabel_button.yml @@ -1,6 +1,6 @@ steps: - target: .lsf-auto-labeling-button placement: bottom - title: 'Scale Your Expertise with Prompts' - content: 'Harness the power of LLMs to automatically label your data with unmatched accuracy and consistency.' + title: 'Great news!' + content: 'You can now rapidly label this project using Label Studio Prompts with the power of LLMs.

Click “Auto-Label Tasks” to automatically label a sample of 20 tasks.' disableBeacon: true From e5a5a87041461f7b6ef6acd4af1d5821470cc5b1 Mon Sep 17 00:00:00 2001 From: nik Date: Thu, 19 Dec 2024 15:58:40 +0000 Subject: [PATCH 17/30] Fix serialiser 'name' error --- label_studio/users/product_tours/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/label_studio/users/product_tours/api.py b/label_studio/users/product_tours/api.py index beacb9e5b2d7..ac8462001c0a 100644 --- a/label_studio/users/product_tours/api.py +++ b/label_studio/users/product_tours/api.py @@ -35,9 +35,9 @@ def get_object(self): tour = UserProductTour.objects.filter(user=self.request.user, name=name).first() if not tour: logger.info(f'Product tour {name} not found for user {self.request.user.id}. Creating new tour.') - tour = self.get_serializer(data={'user': self.request.user.id, 'name': name}) - tour.is_valid(raise_exception=True) - tour.save() + tour_serializer = self.get_serializer(data={'user': self.request.user.id, 'name': name}) + tour_serializer.is_valid(raise_exception=True) + tour = tour_serializer.save() else: logger.info(f'Product tour {name} requested for user {self.request.user.id}.') From 89eb6b7b7f715f4bcf230784743390725295d9c7 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:19:24 +0000 Subject: [PATCH 18/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12431264450 --- poetry.lock | 162 +++---------------------------------------------- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 156 deletions(-) diff --git a/poetry.lock b/poetry.lock index 83ea851df632..911cb16e4645 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1047,21 +1047,6 @@ files = [ [package.extras] tests = ["coverage", "coveralls", "dill", "mock", "nose"] -[[package]] -name = "faker" -version = "33.1.0" -description = "Faker is a Python package that generates fake data for you." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Faker-33.1.0-py3-none-any.whl", hash = "sha256:d30c5f0e2796b8970de68978365247657486eb0311c5abe88d0b895b68dff05d"}, - {file = "faker-33.1.0.tar.gz", hash = "sha256:1c925fc0e86a51fc46648b504078c88d0cd48da1da2595c4e712841cab43a1e4"}, -] - -[package.dependencies] -python-dateutil = ">=2.4" -typing-extensions = "*" - [[package]] name = "fakeredis" version = "1.5.0" @@ -1851,28 +1836,6 @@ files = [ {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, ] -[[package]] -name = "jsf" -version = "0.11.2" -description = "Creates fake JSON files from a JSON schema" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jsf-0.11.2-py3-none-any.whl", hash = "sha256:b4472c8c2d776eb3e0bb08368caa6ae0ead7ea78b20653facc07b6d93768612c"}, - {file = "jsf-0.11.2.tar.gz", hash = "sha256:07055b363281d38ce871a9256a00587d8472802c5108721a7fe5884465104b5d"}, -] - -[package.dependencies] -faker = ">=15.3.4" -jsonschema = ">=4.17.3" -pydantic = ">=2.0.0" -rstr = ">=3.2.0" -smart-open = {version = ">=6.3.0", extras = ["http"]} -typing-extensions = ">=4.9.0" - -[package.extras] -cli = ["typer (>=0.7.0)"] - [[package]] name = "jsonschema" version = "4.23.0" @@ -1933,12 +1896,12 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", [[package]] name = "label-studio-sdk" -version = "1.0.9.dev" +version = "1.0.9" description = "" optional = false -python-versions = ">=3.9,<4" +python-versions = "^3.8" files = [ - {file = "08629d6768128aeb2f7f94fe71504150fe41a561.zip", hash = "sha256:931833bd4d12ac93f2ce51783c25a66999cfa6968f8b6b5ba45dee70a25a3fae"}, + {file = "4ddf26254448553b3e0ea2c280e4ea052b9ca31f.zip", hash = "sha256:eb982d220a2a4a1c8f095f73e31043078e31e06e05c8abfc89b1a9de925acfd7"}, ] [package.dependencies] @@ -1946,11 +1909,10 @@ appdirs = ">=1.4.3" datamodel-code-generator = "0.26.1" httpx = ">=0.21.2" ijson = ">=3.2.3" -jsf = ">=0.11.2,<0.12.0" jsonschema = ">=4.23.0" lxml = ">=4.2.5" nltk = ">=3.9.1,<4.0.0" -numpy = ">=1.26.4,<2.0.0" +numpy = "<2.0.0" pandas = ">=0.24.0" Pillow = ">=10.0.1" pydantic = ">=1.9.2" @@ -1962,7 +1924,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/08629d6768128aeb2f7f94fe71504150fe41a561.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/4ddf26254448553b3e0ea2c280e4ea052b9ca31f.zip" [[package]] name = "launchdarkly-server-sdk" @@ -3476,6 +3438,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3890,17 +3853,6 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" -[[package]] -name = "rstr" -version = "3.2.2" -description = "Generate random strings in Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "rstr-3.2.2-py3-none-any.whl", hash = "sha256:f39195d38da1748331eeec52f1276e71eb6295e7949beea91a5e9af2340d7b3b"}, - {file = "rstr-3.2.2.tar.gz", hash = "sha256:c4a564d4dfb4472d931d145c43d1cf1ad78c24592142e7755b8866179eeac012"}, -] - [[package]] name = "ruamel-yaml" version = "0.18.5" @@ -4115,32 +4067,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "smart-open" -version = "7.0.5" -description = "Utils for streaming large files (S3, HDFS, GCS, Azure Blob Storage, gzip, bz2...)" -optional = false -python-versions = "<4.0,>=3.7" -files = [ - {file = "smart_open-7.0.5-py3-none-any.whl", hash = "sha256:8523ed805c12dff3eaa50e9c903a6cb0ae78800626631c5fe7ea073439847b89"}, - {file = "smart_open-7.0.5.tar.gz", hash = "sha256:d3672003b1dbc85e2013e4983b88eb9a5ccfd389b0d4e5015f39a9ee5620ec18"}, -] - -[package.dependencies] -requests = {version = "*", optional = true, markers = "extra == \"http\""} -wrapt = "*" - -[package.extras] -all = ["azure-common", "azure-core", "azure-storage-blob", "boto3", "google-cloud-storage (>=2.6.0)", "paramiko", "requests", "zstandard"] -azure = ["azure-common", "azure-core", "azure-storage-blob"] -gcs = ["google-cloud-storage (>=2.6.0)"] -http = ["requests"] -s3 = ["boto3"] -ssh = ["paramiko"] -test = ["awscli", "azure-common", "azure-core", "azure-storage-blob", "boto3", "google-cloud-storage (>=2.6.0)", "moto[server]", "numpy", "paramiko", "pyopenssl", "pytest", "pytest-benchmark", "pytest-rerunfailures", "requests", "responses", "zstandard"] -webhdfs = ["requests"] -zst = ["zstandard"] - [[package]] name = "sniffio" version = "1.3.0" @@ -4498,80 +4424,6 @@ files = [ [package.extras] test = ["pytest (>=6.0.0)"] -[[package]] -name = "wrapt" -version = "1.17.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.8" -files = [ - {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"}, - {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"}, - {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"}, - {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"}, - {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"}, - {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"}, - {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"}, - {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"}, - {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"}, - {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"}, - {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"}, - {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"}, - {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"}, - {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"}, - {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"}, - {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"}, - {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"}, - {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"}, - {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"}, - {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"}, - {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"}, - {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"}, - {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, -] - [[package]] name = "xmljson" version = "0.2.1" @@ -4615,4 +4467,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "5124c308584e6f9d7493d37f6d471947f30ab4eaedb1b13c3c6ac9a71c3eaccd" +content-hash = "31688b93764bdb0fe47f8237a29b104ce9b794ea5aa9a7b427174659c260537e" diff --git a/pyproject.toml b/pyproject.toml index b1e374e588cb..306e2faf368d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/08629d6768128aeb2f7f94fe71504150fe41a561.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/4ddf26254448553b3e0ea2c280e4ea052b9ca31f.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From a9c9914c343ff5b2cced4cbca09d5c53163773ed Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:56:49 +0000 Subject: [PATCH 19/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12432646030 --- poetry.lock | 155 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- 2 files changed, 153 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 911cb16e4645..9b72fb46dc9c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1047,6 +1047,21 @@ files = [ [package.extras] tests = ["coverage", "coveralls", "dill", "mock", "nose"] +[[package]] +name = "faker" +version = "33.1.0" +description = "Faker is a Python package that generates fake data for you." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Faker-33.1.0-py3-none-any.whl", hash = "sha256:d30c5f0e2796b8970de68978365247657486eb0311c5abe88d0b895b68dff05d"}, + {file = "faker-33.1.0.tar.gz", hash = "sha256:1c925fc0e86a51fc46648b504078c88d0cd48da1da2595c4e712841cab43a1e4"}, +] + +[package.dependencies] +python-dateutil = ">=2.4" +typing-extensions = "*" + [[package]] name = "fakeredis" version = "1.5.0" @@ -1836,6 +1851,28 @@ files = [ {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, ] +[[package]] +name = "jsf" +version = "0.11.2" +description = "Creates fake JSON files from a JSON schema" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsf-0.11.2-py3-none-any.whl", hash = "sha256:b4472c8c2d776eb3e0bb08368caa6ae0ead7ea78b20653facc07b6d93768612c"}, + {file = "jsf-0.11.2.tar.gz", hash = "sha256:07055b363281d38ce871a9256a00587d8472802c5108721a7fe5884465104b5d"}, +] + +[package.dependencies] +faker = ">=15.3.4" +jsonschema = ">=4.17.3" +pydantic = ">=2.0.0" +rstr = ">=3.2.0" +smart-open = {version = ">=6.3.0", extras = ["http"]} +typing-extensions = ">=4.9.0" + +[package.extras] +cli = ["typer (>=0.7.0)"] + [[package]] name = "jsonschema" version = "4.23.0" @@ -1901,7 +1938,7 @@ description = "" optional = false python-versions = "^3.8" files = [ - {file = "4ddf26254448553b3e0ea2c280e4ea052b9ca31f.zip", hash = "sha256:eb982d220a2a4a1c8f095f73e31043078e31e06e05c8abfc89b1a9de925acfd7"}, + {file = "169ab0417c29f9d0baeea4f3133032d1ab75ca04.zip", hash = "sha256:70d164c17a836147deb738df354562cf60f8f53b9bc1aa9515ff87c8ed71a639"}, ] [package.dependencies] @@ -1909,6 +1946,7 @@ appdirs = ">=1.4.3" datamodel-code-generator = "0.26.1" httpx = ">=0.21.2" ijson = ">=3.2.3" +jsf = ">=0.11.2,<0.12.0" jsonschema = ">=4.23.0" lxml = ">=4.2.5" nltk = ">=3.9.1,<4.0.0" @@ -1924,7 +1962,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/4ddf26254448553b3e0ea2c280e4ea052b9ca31f.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/169ab0417c29f9d0baeea4f3133032d1ab75ca04.zip" [[package]] name = "launchdarkly-server-sdk" @@ -3853,6 +3891,17 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "rstr" +version = "3.2.2" +description = "Generate random strings in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rstr-3.2.2-py3-none-any.whl", hash = "sha256:f39195d38da1748331eeec52f1276e71eb6295e7949beea91a5e9af2340d7b3b"}, + {file = "rstr-3.2.2.tar.gz", hash = "sha256:c4a564d4dfb4472d931d145c43d1cf1ad78c24592142e7755b8866179eeac012"}, +] + [[package]] name = "ruamel-yaml" version = "0.18.5" @@ -4067,6 +4116,32 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "smart-open" +version = "7.1.0" +description = "Utils for streaming large files (S3, HDFS, GCS, Azure Blob Storage, gzip, bz2...)" +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "smart_open-7.1.0-py3-none-any.whl", hash = "sha256:4b8489bb6058196258bafe901730c7db0dcf4f083f316e97269c66f45502055b"}, + {file = "smart_open-7.1.0.tar.gz", hash = "sha256:a4f09f84f0f6d3637c6543aca7b5487438877a21360e7368ccf1f704789752ba"}, +] + +[package.dependencies] +requests = {version = "*", optional = true, markers = "extra == \"http\""} +wrapt = "*" + +[package.extras] +all = ["azure-common", "azure-core", "azure-storage-blob", "boto3", "google-cloud-storage (>=2.6.0)", "paramiko", "requests", "zstandard"] +azure = ["azure-common", "azure-core", "azure-storage-blob"] +gcs = ["google-cloud-storage (>=2.6.0)"] +http = ["requests"] +s3 = ["boto3"] +ssh = ["paramiko"] +test = ["awscli", "azure-common", "azure-core", "azure-storage-blob", "boto3", "google-cloud-storage (>=2.6.0)", "moto[server]", "numpy", "paramiko", "pyopenssl", "pytest", "pytest-benchmark", "pytest-rerunfailures", "requests", "responses", "zstandard"] +webhdfs = ["requests"] +zst = ["zstandard"] + [[package]] name = "sniffio" version = "1.3.0" @@ -4424,6 +4499,80 @@ files = [ [package.extras] test = ["pytest (>=6.0.0)"] +[[package]] +name = "wrapt" +version = "1.17.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +files = [ + {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"}, + {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"}, + {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"}, + {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"}, + {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"}, + {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"}, + {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"}, + {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"}, + {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"}, + {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"}, + {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"}, + {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"}, + {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"}, + {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"}, + {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"}, + {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"}, + {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"}, + {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"}, + {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"}, + {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"}, + {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"}, + {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"}, + {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, +] + [[package]] name = "xmljson" version = "0.2.1" @@ -4467,4 +4616,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "31688b93764bdb0fe47f8237a29b104ce9b794ea5aa9a7b427174659c260537e" +content-hash = "2bb9c55ca9d9e458016f86586ab5c4b7b1fc2150c4ef867e58993b0f1b389981" diff --git a/pyproject.toml b/pyproject.toml index 306e2faf368d..d5569e117df3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/4ddf26254448553b3e0ea2c280e4ea052b9ca31f.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/169ab0417c29f9d0baeea4f3133032d1ab75ca04.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From c4edaac261de453842f914b79c4db931dbb1a6e9 Mon Sep 17 00:00:00 2001 From: Matt Bernstein Date: Fri, 20 Dec 2024 15:35:18 +0000 Subject: [PATCH 20/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12434128448 --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9b72fb46dc9c..e42277e5583d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1936,9 +1936,9 @@ name = "label-studio-sdk" version = "1.0.9" description = "" optional = false -python-versions = "^3.8" +python-versions = "^3.9" files = [ - {file = "169ab0417c29f9d0baeea4f3133032d1ab75ca04.zip", hash = "sha256:70d164c17a836147deb738df354562cf60f8f53b9bc1aa9515ff87c8ed71a639"}, + {file = "660cd792246b1dc2ed2b07ec9eab1d67c8c9964e.zip", hash = "sha256:f528d4906e0c06a3fb4d2c1055eaa17a325e75e2745ddbb776b51285a209e486"}, ] [package.dependencies] @@ -1950,7 +1950,7 @@ jsf = ">=0.11.2,<0.12.0" jsonschema = ">=4.23.0" lxml = ">=4.2.5" nltk = ">=3.9.1,<4.0.0" -numpy = "<2.0.0" +numpy = ">=1.26.4,<2.0.0" pandas = ">=0.24.0" Pillow = ">=10.0.1" pydantic = ">=1.9.2" @@ -1962,7 +1962,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/169ab0417c29f9d0baeea4f3133032d1ab75ca04.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/660cd792246b1dc2ed2b07ec9eab1d67c8c9964e.zip" [[package]] name = "launchdarkly-server-sdk" @@ -4616,4 +4616,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "2bb9c55ca9d9e458016f86586ab5c4b7b1fc2150c4ef867e58993b0f1b389981" +content-hash = "ac4ed26d4fdfa49c4ca7d7a1bffc1bd1c9c0d3fda6a96c39d6fbb6e6dd00bfff" diff --git a/pyproject.toml b/pyproject.toml index d5569e117df3..0a97422b8abd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/169ab0417c29f9d0baeea4f3133032d1ab75ca04.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/660cd792246b1dc2ed2b07ec9eab1d67c8c9964e.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From 9706ac1aeb1b88a2969a9c5ffb10cee60474cbae Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:41:21 +0000 Subject: [PATCH 21/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12435080633 --- poetry.lock | 9 +++++---- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index e42277e5583d..3ff557921358 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1936,9 +1936,9 @@ name = "label-studio-sdk" version = "1.0.9" description = "" optional = false -python-versions = "^3.9" +python-versions = ">=3.9,<4" files = [ - {file = "660cd792246b1dc2ed2b07ec9eab1d67c8c9964e.zip", hash = "sha256:f528d4906e0c06a3fb4d2c1055eaa17a325e75e2745ddbb776b51285a209e486"}, + {file = "33998b68cdd2fe0ca07f65d0d650977022a22c9d.zip", hash = "sha256:cc71bbe08e846703765ddcf940b8724522240a166f63879d5fc680cc778888dd"}, ] [package.dependencies] @@ -1954,6 +1954,7 @@ numpy = ">=1.26.4,<2.0.0" pandas = ">=0.24.0" Pillow = ">=10.0.1" pydantic = ">=1.9.2" +pydantic-core = ">=2.18.2,<3.0.0" requests = ">=2.22.0" requests-mock = "1.12.1" typing_extensions = ">=4.0.0" @@ -1962,7 +1963,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/660cd792246b1dc2ed2b07ec9eab1d67c8c9964e.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/33998b68cdd2fe0ca07f65d0d650977022a22c9d.zip" [[package]] name = "launchdarkly-server-sdk" @@ -4616,4 +4617,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "ac4ed26d4fdfa49c4ca7d7a1bffc1bd1c9c0d3fda6a96c39d6fbb6e6dd00bfff" +content-hash = "4e6460efa29f570506fd1d4e1a9fed0917fcea63f2ec198ea7d5e827eb85fe55" diff --git a/pyproject.toml b/pyproject.toml index 0a97422b8abd..e0e10f661690 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/660cd792246b1dc2ed2b07ec9eab1d67c8c9964e.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/33998b68cdd2fe0ca07f65d0d650977022a22c9d.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From b93490bb15e5f64c5651b915410cbb73d56dad9f Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:27:06 +0000 Subject: [PATCH 22/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12436508850 --- poetry.lock | 7 +++---- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3ff557921358..c724cc20bfe5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1938,7 +1938,7 @@ description = "" optional = false python-versions = ">=3.9,<4" files = [ - {file = "33998b68cdd2fe0ca07f65d0d650977022a22c9d.zip", hash = "sha256:cc71bbe08e846703765ddcf940b8724522240a166f63879d5fc680cc778888dd"}, + {file = "264ad59a0986d7ca90e7b95ffacc6244d1b8171c.zip", hash = "sha256:b80a29cddb0c6f2262d178dbaf28943f8eb4b00ef7bf9e18d357599997caecb0"}, ] [package.dependencies] @@ -1954,7 +1954,6 @@ numpy = ">=1.26.4,<2.0.0" pandas = ">=0.24.0" Pillow = ">=10.0.1" pydantic = ">=1.9.2" -pydantic-core = ">=2.18.2,<3.0.0" requests = ">=2.22.0" requests-mock = "1.12.1" typing_extensions = ">=4.0.0" @@ -1963,7 +1962,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/33998b68cdd2fe0ca07f65d0d650977022a22c9d.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/264ad59a0986d7ca90e7b95ffacc6244d1b8171c.zip" [[package]] name = "launchdarkly-server-sdk" @@ -4617,4 +4616,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "4e6460efa29f570506fd1d4e1a9fed0917fcea63f2ec198ea7d5e827eb85fe55" +content-hash = "faf764bc6d90cf387e5f04f2d795a1daea6c2e7262f65a7ef32077bc66830617" diff --git a/pyproject.toml b/pyproject.toml index e0e10f661690..74e0b7b34ca1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/33998b68cdd2fe0ca07f65d0d650977022a22c9d.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/264ad59a0986d7ca90e7b95ffacc6244d1b8171c.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From 8bc6cf4cf21131267b98317caa094a50a534d7ff Mon Sep 17 00:00:00 2001 From: nik Date: Sun, 22 Dec 2024 11:56:57 +0200 Subject: [PATCH 23/30] Address comments --- .../users/migrations/0010_userproducttour.py | 8 +++--- label_studio/users/product_tours/api.py | 4 +-- label_studio/users/product_tours/models.py | 25 +++++++++++-------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/label_studio/users/migrations/0010_userproducttour.py b/label_studio/users/migrations/0010_userproducttour.py index ffe9395c64f7..0238f2aad40d 100644 --- a/label_studio/users/migrations/0010_userproducttour.py +++ b/label_studio/users/migrations/0010_userproducttour.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.15 on 2024-12-17 18:56 +# Generated by Django 4.2.15 on 2024-12-22 09:54 from django.conf import settings from django.db import migrations, models @@ -16,9 +16,9 @@ class Migration(migrations.Migration): name='UserProductTour', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='Unique identifier for the product tour. Name must match the config name.', max_length=256)), - ('state', models.CharField(choices=[('ready', 'ready'), ('completed', 'completed'), ('skipped', 'skipped')], default='ready', help_text='Current state of the tour for this user: "ready" when tour is initiated, "completed" when user finishes the tour, "skipped" when user cancels the tour.', max_length=32)), - ('interaction_data', models.JSONField(blank=True, default=dict, help_text='Additional data about user interaction with the tour')), + ('name', models.CharField(help_text='Unique identifier for the product tour. Name must match the config name.', max_length=256, verbose_name='Name')), + ('state', models.CharField(choices=[('ready', 'Ready'), ('completed', 'Completed'), ('skipped', 'Skipped')], default='ready', help_text='Current state of the tour for this user. Available options: ready (Ready), completed (Completed), skipped (Skipped)', max_length=32, verbose_name='State')), + ('interaction_data', models.JSONField(blank=True, default=dict, help_text='Additional data about user interaction with the tour', verbose_name='Interaction Data')), ('created_at', models.DateTimeField(auto_now_add=True, help_text='When this tour record was created')), ('updated_at', models.DateTimeField(auto_now=True, help_text='When this tour record was last updated')), ('user', models.ForeignKey(help_text='User who interacted with the tour', on_delete=django.db.models.deletion.CASCADE, related_name='tours', to=settings.AUTH_USER_MODEL)), diff --git a/label_studio/users/product_tours/api.py b/label_studio/users/product_tours/api.py index ac8462001c0a..5643435ab81c 100644 --- a/label_studio/users/product_tours/api.py +++ b/label_studio/users/product_tours/api.py @@ -34,11 +34,11 @@ def get_object(self): tour = UserProductTour.objects.filter(user=self.request.user, name=name).first() if not tour: - logger.info(f'Product tour {name} not found for user {self.request.user.id}. Creating new tour.') + logger.debug(f'Product tour {name} not found for user {self.request.user.id}. Creating new tour.') tour_serializer = self.get_serializer(data={'user': self.request.user.id, 'name': name}) tour_serializer.is_valid(raise_exception=True) tour = tour_serializer.save() else: - logger.info(f'Product tour {name} requested for user {self.request.user.id}.') + logger.debug(f'Product tour {name} requested for user {self.request.user.id}.') return tour diff --git a/label_studio/users/product_tours/models.py b/label_studio/users/product_tours/models.py index d196eb353178..a117455a926a 100644 --- a/label_studio/users/product_tours/models.py +++ b/label_studio/users/product_tours/models.py @@ -1,14 +1,14 @@ from enum import Enum from typing import Any, Dict, Optional - +from django.utils.translation import gettext_lazy as _ from django.db import models from pydantic import BaseModel, Field -class ProductTourState(str, Enum): - READY = 'ready' - COMPLETED = 'completed' - SKIPPED = 'skipped' +class ProductTourState(models.TextChoices): + READY = 'ready', _('Ready') + COMPLETED = 'completed', _('Completed') + SKIPPED = 'skipped', _('Skipped') class ProductTourInteractionData(BaseModel): @@ -31,18 +31,23 @@ class UserProductTour(models.Model): ) name = models.CharField( - max_length=256, help_text='Unique identifier for the product tour. Name must match the config name.' + _('Name'), + max_length=256, + help_text='Unique identifier for the product tour. Name must match the config name.' ) state = models.CharField( + _('State'), max_length=32, - choices=[(state.value, state.value) for state in ProductTourState], - default=ProductTourState.READY.value, - help_text='Current state of the tour for this user: "ready" when tour is initiated, "completed" when user finishes the tour, "skipped" when user cancels the tour.', + choices=ProductTourState.choices, + default=ProductTourState.READY, + help_text=f'Current state of the tour for this user. Available options: {", ".join(f"{k} ({v})" for k,v in ProductTourState.choices)}', ) interaction_data = models.JSONField( - default=dict, blank=True, help_text='Additional data about user interaction with the tour' + _('Interaction Data'), + default=dict, blank=True, + help_text='Additional data about user interaction with the tour' ) created_at = models.DateTimeField(auto_now_add=True, help_text='When this tour record was created') From 409bfaf286ef0bd74013ff49d0482bebe13863b6 Mon Sep 17 00:00:00 2001 From: nik Date: Sun, 22 Dec 2024 21:59:19 +0300 Subject: [PATCH 24/30] Update joyride content --- .../product_tours/configs/show_autolabel_button.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/label_studio/users/product_tours/configs/show_autolabel_button.yml b/label_studio/users/product_tours/configs/show_autolabel_button.yml index 6a7506bf8539..fe87239d3f8d 100644 --- a/label_studio/users/product_tours/configs/show_autolabel_button.yml +++ b/label_studio/users/product_tours/configs/show_autolabel_button.yml @@ -2,5 +2,13 @@ steps: - target: .lsf-auto-labeling-button placement: bottom title: 'Great news!' - content: 'You can now rapidly label this project using Label Studio Prompts with the power of LLMs.

Click “Auto-Label Tasks” to automatically label a sample of 20 tasks.' + content: > + You can now rapidly label this project using Prompts. +

+ Click "Auto-Label Tasks" to set up LLM powered labeling in under a minute. +

+ We've provided some OpenAI credits to get you started. disableBeacon: true + locale: + last: "OK" + skip: "Don't show this message again." From fc3eab3244f78d31a116d5509b618cc39564f0a5 Mon Sep 17 00:00:00 2001 From: nik Date: Mon, 23 Dec 2024 19:36:43 +0300 Subject: [PATCH 25/30] Ignore migrations in linter --- .../users/migrations/0010_userproducttour.py | 2 ++ label_studio/users/product_tours/models.py | 13 ++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/label_studio/users/migrations/0010_userproducttour.py b/label_studio/users/migrations/0010_userproducttour.py index 0238f2aad40d..122fb137d49c 100644 --- a/label_studio/users/migrations/0010_userproducttour.py +++ b/label_studio/users/migrations/0010_userproducttour.py @@ -3,6 +3,7 @@ from django.conf import settings from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -12,6 +13,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='UserProductTour', fields=[ diff --git a/label_studio/users/product_tours/models.py b/label_studio/users/product_tours/models.py index a117455a926a..a8970109e179 100644 --- a/label_studio/users/product_tours/models.py +++ b/label_studio/users/product_tours/models.py @@ -1,7 +1,7 @@ -from enum import Enum from typing import Any, Dict, Optional -from django.utils.translation import gettext_lazy as _ + from django.db import models +from django.utils.translation import gettext_lazy as _ from pydantic import BaseModel, Field @@ -31,9 +31,7 @@ class UserProductTour(models.Model): ) name = models.CharField( - _('Name'), - max_length=256, - help_text='Unique identifier for the product tour. Name must match the config name.' + _('Name'), max_length=256, help_text='Unique identifier for the product tour. Name must match the config name.' ) state = models.CharField( @@ -46,8 +44,9 @@ class UserProductTour(models.Model): interaction_data = models.JSONField( _('Interaction Data'), - default=dict, blank=True, - help_text='Additional data about user interaction with the tour' + default=dict, + blank=True, + help_text='Additional data about user interaction with the tour', ) created_at = models.DateTimeField(auto_now_add=True, help_text='When this tour record was created') From c294c624b06e3d6ec377a9d964368982bd649bdf Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:19:14 +0000 Subject: [PATCH 26/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12496970745 --- poetry.lock | 7 ++++--- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index c724cc20bfe5..0cf383aeecf0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1938,7 +1938,7 @@ description = "" optional = false python-versions = ">=3.9,<4" files = [ - {file = "264ad59a0986d7ca90e7b95ffacc6244d1b8171c.zip", hash = "sha256:b80a29cddb0c6f2262d178dbaf28943f8eb4b00ef7bf9e18d357599997caecb0"}, + {file = "aa8e0d8e0b54d88bcdfcbd53e17cdb267535dbef.zip", hash = "sha256:a2db14ae19055b3fbe94ce358e2ef3059ad8c33edcc9a0a61e045ce3d880a276"}, ] [package.dependencies] @@ -1954,6 +1954,7 @@ numpy = ">=1.26.4,<2.0.0" pandas = ">=0.24.0" Pillow = ">=10.0.1" pydantic = ">=1.9.2" +pydantic-core = ">=2.18.2,<3.0.0" requests = ">=2.22.0" requests-mock = "1.12.1" typing_extensions = ">=4.0.0" @@ -1962,7 +1963,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/264ad59a0986d7ca90e7b95ffacc6244d1b8171c.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/aa8e0d8e0b54d88bcdfcbd53e17cdb267535dbef.zip" [[package]] name = "launchdarkly-server-sdk" @@ -4616,4 +4617,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "faf764bc6d90cf387e5f04f2d795a1daea6c2e7262f65a7ef32077bc66830617" +content-hash = "886a942bc464927d9c1945a19cd8198d1c683933546752ed22128632bb2866a5" diff --git a/pyproject.toml b/pyproject.toml index 74e0b7b34ca1..bd7f3903495c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/264ad59a0986d7ca90e7b95ffacc6244d1b8171c.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/aa8e0d8e0b54d88bcdfcbd53e17cdb267535dbef.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From 154e8202bae57f23d3a7a11b165d1eabac7999f5 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:35:48 +0000 Subject: [PATCH 27/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12497084177 --- poetry.lock | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0cf383aeecf0..08d42c0236e3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1938,7 +1938,7 @@ description = "" optional = false python-versions = ">=3.9,<4" files = [ - {file = "aa8e0d8e0b54d88bcdfcbd53e17cdb267535dbef.zip", hash = "sha256:a2db14ae19055b3fbe94ce358e2ef3059ad8c33edcc9a0a61e045ce3d880a276"}, + {file = "33fc2f99d3a955ea5f989221af1766bf60bd0fa6.zip", hash = "sha256:fcebe3f053b36a5bc5c1cb205527e824c88303a4ae292304f68da919f8de1c8f"}, ] [package.dependencies] @@ -1963,7 +1963,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/aa8e0d8e0b54d88bcdfcbd53e17cdb267535dbef.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/33fc2f99d3a955ea5f989221af1766bf60bd0fa6.zip" [[package]] name = "launchdarkly-server-sdk" @@ -4617,4 +4617,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "886a942bc464927d9c1945a19cd8198d1c683933546752ed22128632bb2866a5" +content-hash = "9f8ceae8dc196634463fbcbdd595fc069cf647caad0f8e52ca8d78659c8578c1" diff --git a/pyproject.toml b/pyproject.toml index bd7f3903495c..59c331d46e7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/aa8e0d8e0b54d88bcdfcbd53e17cdb267535dbef.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/33fc2f99d3a955ea5f989221af1766bf60bd0fa6.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From 58b4cb403515f01c92b8396361e8485189bbbb45 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:41:35 +0000 Subject: [PATCH 28/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12497120230 --- poetry.lock | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 08d42c0236e3..00cd33ae5bc5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1938,7 +1938,7 @@ description = "" optional = false python-versions = ">=3.9,<4" files = [ - {file = "33fc2f99d3a955ea5f989221af1766bf60bd0fa6.zip", hash = "sha256:fcebe3f053b36a5bc5c1cb205527e824c88303a4ae292304f68da919f8de1c8f"}, + {file = "da85759c0e30fae4c22acf34589c97e850716688.zip", hash = "sha256:e31749e5446ee452751251e755f96cf3ab29d2b1d2b86262461f7291f5d3f486"}, ] [package.dependencies] @@ -1963,7 +1963,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/33fc2f99d3a955ea5f989221af1766bf60bd0fa6.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/da85759c0e30fae4c22acf34589c97e850716688.zip" [[package]] name = "launchdarkly-server-sdk" @@ -4617,4 +4617,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "9f8ceae8dc196634463fbcbdd595fc069cf647caad0f8e52ca8d78659c8578c1" +content-hash = "64731c3151fd15e90bd4d6202c4987e83393d55c9d021b4dde1a66249d1a975a" diff --git a/pyproject.toml b/pyproject.toml index 59c331d46e7c..eb2ae2f517d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/33fc2f99d3a955ea5f989221af1766bf60bd0fa6.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/da85759c0e30fae4c22acf34589c97e850716688.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From 3697f0327ac222cc3fefdb3cf385f7b2ddf8d198 Mon Sep 17 00:00:00 2001 From: nik Date: Thu, 26 Dec 2024 13:59:42 +0300 Subject: [PATCH 29/30] Fix target selector --- .../users/product_tours/configs/show_autolabel_button.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/label_studio/users/product_tours/configs/show_autolabel_button.yml b/label_studio/users/product_tours/configs/show_autolabel_button.yml index fe87239d3f8d..b88ce2959dfb 100644 --- a/label_studio/users/product_tours/configs/show_autolabel_button.yml +++ b/label_studio/users/product_tours/configs/show_autolabel_button.yml @@ -1,5 +1,5 @@ steps: - - target: .lsf-auto-labeling-button + - target: '[data-testid="auto-labeling-button"]' placement: bottom title: 'Great news!' content: > @@ -11,4 +11,4 @@ steps: disableBeacon: true locale: last: "OK" - skip: "Don't show this message again." + skip: "Don't show this message again." \ No newline at end of file From eba5fdfad7dfd0363392fff50b9124e8172e53de Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2024 10:01:26 +0000 Subject: [PATCH 30/30] [submodules] Bump HumanSignal/label-studio-sdk version Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12525672032 --- poetry.lock | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 00cd33ae5bc5..c93ad37ed3e9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1938,7 +1938,7 @@ description = "" optional = false python-versions = ">=3.9,<4" files = [ - {file = "da85759c0e30fae4c22acf34589c97e850716688.zip", hash = "sha256:e31749e5446ee452751251e755f96cf3ab29d2b1d2b86262461f7291f5d3f486"}, + {file = "c73b170f38879febaa6f38e60a994bf35a3e6ceb.zip", hash = "sha256:b734dea7deb8fbc4286b5e39acc852e25b5f01e8537f2e37ffa347a9aa17024c"}, ] [package.dependencies] @@ -1963,7 +1963,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/da85759c0e30fae4c22acf34589c97e850716688.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/c73b170f38879febaa6f38e60a994bf35a3e6ceb.zip" [[package]] name = "launchdarkly-server-sdk" @@ -4617,4 +4617,4 @@ uwsgi = ["pyuwsgi", "uwsgitop"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4" -content-hash = "64731c3151fd15e90bd4d6202c4987e83393d55c9d021b4dde1a66249d1a975a" +content-hash = "c07e351b78938a2e3ca3f8dab2594cfe89a3fe077035c3a174693548ae25d5f9" diff --git a/pyproject.toml b/pyproject.toml index eb2ae2f517d3..51071a82bc96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ django-migration-linter = "^5.1.0" setuptools = ">=75.4.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/da85759c0e30fae4c22acf34589c97e850716688.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/c73b170f38879febaa6f38e60a994bf35a3e6ceb.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2"