mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 12:03:46 +00:00 
			
		
		
		
	settings: Add setting to control permission for topic summarization.
This commit is contained in:
		| @@ -24,6 +24,10 @@ format used by the Zulip server that they are interacting with. | ||||
|  | ||||
| * [`POST /register`](/api/register-queue): Added | ||||
|   `server_can_summarize_topics` to the response. | ||||
| * [`POST /register`](/api/register-queue), [`POST /events`](/api/get-events), | ||||
|   `PATCH /realm`: Added `can_summarize_topics_group` realm setting which is | ||||
|   a [group-setting value](/api/group-setting-values) describing the set of | ||||
|   users with permission to use AI summarization. | ||||
|  | ||||
| **Feature level 349** | ||||
|  | ||||
|   | ||||
| @@ -256,6 +256,7 @@ export function build_page(): void { | ||||
|         giphy_help_link, | ||||
|         ...get_realm_level_notification_settings(), | ||||
|         group_setting_labels: settings_config.all_group_setting_labels.realm, | ||||
|         server_can_summarize_topics: realm.server_can_summarize_topics, | ||||
|     }; | ||||
|  | ||||
|     const rendered_admin_tab = render_admin_tab(options); | ||||
|   | ||||
| @@ -56,6 +56,7 @@ export const realm_group_setting_name_schema = z.enum([ | ||||
|     "can_manage_all_groups", | ||||
|     "can_move_messages_between_channels_group", | ||||
|     "can_move_messages_between_topics_group", | ||||
|     "can_summarize_topics_group", | ||||
|     "create_multiuse_invite_group", | ||||
|     "direct_message_initiator_group", | ||||
|     "direct_message_permission_group", | ||||
|   | ||||
| @@ -56,7 +56,6 @@ type TopicPopoverContext = { | ||||
|     topic_unmuted: boolean; | ||||
|     is_spectator: boolean; | ||||
|     is_moderator: boolean; | ||||
|     is_development_environment: boolean; | ||||
|     is_topic_empty: boolean; | ||||
|     can_move_topic: boolean; | ||||
|     can_rename_topic: boolean; | ||||
| @@ -67,6 +66,7 @@ type TopicPopoverContext = { | ||||
|     url: string; | ||||
|     visibility_policy: number | false; | ||||
|     all_visibility_policies: AllVisibilityPolicies; | ||||
|     can_summarize_topics: boolean; | ||||
| }; | ||||
|  | ||||
| type VisibilityChangePopoverContext = { | ||||
| @@ -267,8 +267,6 @@ export function get_topic_popover_content_context({ | ||||
|         can_move_topic, | ||||
|         can_rename_topic, | ||||
|         is_moderator: current_user.is_moderator, | ||||
|         // Temporary, as we're using this to control whether we show the summarize popover. | ||||
|         is_development_environment: page_params.development_environment, | ||||
|         is_realm_admin: current_user.is_admin, | ||||
|         topic_is_resolved: resolved_topic.is_resolved(topic_name), | ||||
|         has_starred_messages, | ||||
| @@ -276,6 +274,8 @@ export function get_topic_popover_content_context({ | ||||
|         url, | ||||
|         visibility_policy, | ||||
|         all_visibility_policies, | ||||
|         can_summarize_topics: | ||||
|             realm.server_can_summarize_topics && settings_data.user_can_summarize_topics(), | ||||
|     }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -225,6 +225,7 @@ export function dispatch_normal_event(event) { | ||||
|                 can_manage_all_groups: noop, | ||||
|                 can_move_messages_between_channels_group: noop, | ||||
|                 can_move_messages_between_topics_group: noop, | ||||
|                 can_summarize_topics_group: noop, | ||||
|                 create_multiuse_invite_group: noop, | ||||
|                 default_code_block_language: noop, | ||||
|                 default_language: noop, | ||||
|   | ||||
| @@ -814,6 +814,7 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea | ||||
|         case "realm_can_manage_all_groups": | ||||
|         case "realm_can_move_messages_between_channels_group": | ||||
|         case "realm_can_move_messages_between_topics_group": | ||||
|         case "realm_can_summarize_topics_group": | ||||
|         case "realm_create_multiuse_invite_group": | ||||
|         case "realm_direct_message_initiator_group": | ||||
|         case "realm_direct_message_permission_group": { | ||||
| @@ -1064,6 +1065,7 @@ export function populate_data_for_realm_settings_request( | ||||
|                     "can_invite_users_group", | ||||
|                     "can_move_messages_between_channels_group", | ||||
|                     "can_move_messages_between_topics_group", | ||||
|                     "can_summarize_topics_group", | ||||
|                     "create_multiuse_invite_group", | ||||
|                     "direct_message_initiator_group", | ||||
|                     "direct_message_permission_group", | ||||
| @@ -1534,6 +1536,7 @@ export const group_setting_widget_map = new Map<string, GroupSettingPillContaine | ||||
|     ["realm_can_manage_all_groups", null], | ||||
|     ["realm_can_move_messages_between_channels_group", null], | ||||
|     ["realm_can_move_messages_between_topics_group", null], | ||||
|     ["realm_can_summarize_topics_group", null], | ||||
|     ["realm_create_multiuse_invite_group", null], | ||||
|     ["realm_direct_message_initiator_group", null], | ||||
|     ["realm_direct_message_permission_group", null], | ||||
|   | ||||
| @@ -687,6 +687,7 @@ export const all_group_setting_labels = { | ||||
|         can_access_all_users_group: $t({ | ||||
|             defaultMessage: "Who can view all other users in the organization", | ||||
|         }), | ||||
|         can_summarize_topics_group: $t({defaultMessage: "Who can use AI summaries"}), | ||||
|         can_create_write_only_bots_group: $t({ | ||||
|             defaultMessage: "Who can create bots that send messages into Zulip", | ||||
|         }), | ||||
| @@ -766,6 +767,7 @@ export const realm_group_permission_settings: { | ||||
|         subsection_heading: $t({defaultMessage: "Other permissions"}), | ||||
|         subsection_key: "org-other-permissions", | ||||
|         settings: [ | ||||
|             "can_summarize_topics_group", | ||||
|             "can_create_write_only_bots_group", | ||||
|             "can_create_bots_group", | ||||
|             "can_add_custom_emoji_group", | ||||
|   | ||||
| @@ -90,6 +90,14 @@ export function user_can_create_multiuse_invite(): boolean { | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export function user_can_summarize_topics(): boolean { | ||||
|     return user_has_permission_for_group_setting( | ||||
|         realm.realm_can_summarize_topics_group, | ||||
|         "can_summarize_topics_group", | ||||
|         "realm", | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export function can_subscribe_others_to_all_accessible_streams(): boolean { | ||||
|     return user_has_permission_for_group_setting( | ||||
|         realm.realm_can_add_subscribers_group, | ||||
|   | ||||
| @@ -533,6 +533,7 @@ export function discard_realm_property_element_changes(elem: HTMLElement): void | ||||
|         case "realm_can_manage_all_groups": | ||||
|         case "realm_can_move_messages_between_channels_group": | ||||
|         case "realm_can_move_messages_between_topics_group": | ||||
|         case "realm_can_summarize_topics_group": | ||||
|         case "realm_create_multiuse_invite_group": | ||||
|         case "realm_direct_message_initiator_group": | ||||
|         case "realm_direct_message_permission_group": { | ||||
|   | ||||
| @@ -307,6 +307,7 @@ export const realm_schema = z.object({ | ||||
|     realm_can_manage_all_groups: group_setting_value_schema, | ||||
|     realm_can_move_messages_between_channels_group: group_setting_value_schema, | ||||
|     realm_can_move_messages_between_topics_group: group_setting_value_schema, | ||||
|     realm_can_summarize_topics_group: group_setting_value_schema, | ||||
|     realm_create_multiuse_invite_group: group_setting_value_schema, | ||||
|     realm_date_created: z.number(), | ||||
|     realm_default_code_block_language: z.string(), | ||||
|   | ||||
| @@ -506,6 +506,10 @@ input[type="checkbox"] { | ||||
|     padding-top: 10px; | ||||
| } | ||||
|  | ||||
| #org-other-permissions .tip { | ||||
|     margin-top: 0; | ||||
| } | ||||
|  | ||||
| .language_selection_widget .language_selection_button { | ||||
|     text-decoration: none; | ||||
|     min-width: 0; | ||||
|   | ||||
| @@ -38,7 +38,7 @@ | ||||
|         {{else}} | ||||
|             {{!-- Group 2 --}} | ||||
|             <li role="separator" class="popover-menu-separator"></li> | ||||
|             {{#if (and is_development_environment (or is_moderator is_realm_admin))}} | ||||
|             {{#if can_summarize_topics}} | ||||
|             <li role="none" class="link-item popover-menu-list-item"> | ||||
|                 <a role="menuitem" class="sidebar-popover-summarize-topic popover-menu-link" tabindex="0"> | ||||
|                     <i class="popover-menu-icon fa fa-magic" aria-hidden="true"></i> | ||||
|   | ||||
| @@ -341,6 +341,15 @@ | ||||
|                 {{> settings_save_discard_widget section_name="other-permissions" }} | ||||
|             </div> | ||||
|             <div class="m-10 inline-block organization-permissions-parent"> | ||||
|                 {{#unless server_can_summarize_topics}} | ||||
|                     <div class="tip"> | ||||
|                         {{t "AI summaries are not enabled on this server."}} | ||||
|                     </div> | ||||
|                 {{/unless}} | ||||
|                 {{> group_setting_value_pill_input | ||||
|                   setting_name="realm_can_summarize_topics_group" | ||||
|                   label=group_setting_labels.can_summarize_topics_group}} | ||||
|  | ||||
|                 {{> group_setting_value_pill_input | ||||
|                   setting_name="realm_can_create_write_only_bots_group" | ||||
|                   label=group_setting_labels.can_create_write_only_bots_group}} | ||||
|   | ||||
| @@ -632,3 +632,10 @@ run_test("guests_can_access_all_other_users", () => { | ||||
|     realm.realm_can_access_all_users_group = everyone.id; | ||||
|     assert.ok(settings_data.guests_can_access_all_other_users()); | ||||
| }); | ||||
|  | ||||
| run_test("user_can_summarize_topics", () => { | ||||
|     test_realm_group_settings( | ||||
|         "realm_can_summarize_topics_group", | ||||
|         settings_data.user_can_summarize_topics, | ||||
|     ); | ||||
| }); | ||||
|   | ||||
| @@ -524,6 +524,7 @@ class GroupSettingUpdateData(GroupSettingUpdateDataCore): | ||||
|     can_manage_all_groups: int | AnonymousSettingGroupDict | None = None | ||||
|     can_move_messages_between_channels_group: int | AnonymousSettingGroupDict | None = None | ||||
|     can_move_messages_between_topics_group: int | AnonymousSettingGroupDict | None = None | ||||
|     can_summarize_topics_group: int | AnonymousSettingGroupDict | None = None | ||||
|     direct_message_initiator_group: int | AnonymousSettingGroupDict | None = None | ||||
|     direct_message_permission_group: int | AnonymousSettingGroupDict | None = None | ||||
|  | ||||
|   | ||||
							
								
								
									
										23
									
								
								zerver/migrations/0664_realm_can_summarize_topics_group.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								zerver/migrations/0664_realm_can_summarize_topics_group.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| # Generated by Django 5.0.10 on 2025-02-10 10:31 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     dependencies = [ | ||||
|         ("zerver", "0663_realm_enable_guest_user_dm_warning"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="realm", | ||||
|             name="can_summarize_topics_group", | ||||
|             field=models.ForeignKey( | ||||
|                 null=True, | ||||
|                 on_delete=django.db.models.deletion.RESTRICT, | ||||
|                 related_name="+", | ||||
|                 to="zerver.usergroup", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,35 @@ | ||||
| # Generated by Django 5.0.10 on 2025-02-10 10:31 | ||||
|  | ||||
| from django.db import migrations | ||||
| from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||||
| from django.db.migrations.state import StateApps | ||||
| from django.db.models import OuterRef | ||||
|  | ||||
|  | ||||
| def set_default_for_can_summarize_topics_group( | ||||
|     apps: StateApps, schema_editor: BaseDatabaseSchemaEditor | ||||
| ) -> None: | ||||
|     Realm = apps.get_model("zerver", "Realm") | ||||
|     NamedUserGroup = apps.get_model("zerver", "NamedUserGroup") | ||||
|  | ||||
|     Realm.objects.filter(can_summarize_topics_group=None).update( | ||||
|         can_summarize_topics_group=NamedUserGroup.objects.filter( | ||||
|             name="role:everyone", realm=OuterRef("id"), is_system_group=True | ||||
|         ).values("pk") | ||||
|     ) | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     atomic = False | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("zerver", "0664_realm_can_summarize_topics_group"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.RunPython( | ||||
|             set_default_for_can_summarize_topics_group, | ||||
|             elidable=True, | ||||
|             reverse_code=migrations.RunPython.noop, | ||||
|         ) | ||||
|     ] | ||||
| @@ -0,0 +1,22 @@ | ||||
| # Generated by Django 5.0.10 on 2025-02-10 10:36 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     dependencies = [ | ||||
|         ("zerver", "0665_set_default_for_can_summarize_topics_group"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="realm", | ||||
|             name="can_summarize_topics_group", | ||||
|             field=models.ForeignKey( | ||||
|                 on_delete=django.db.models.deletion.RESTRICT, | ||||
|                 related_name="+", | ||||
|                 to="zerver.usergroup", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @@ -293,6 +293,11 @@ class Realm(models.Model):  # type: ignore[django-manager-missing] # django-stub | ||||
|         "UserGroup", on_delete=models.RESTRICT, related_name="+" | ||||
|     ) | ||||
|  | ||||
|     # UserGroup whose members are allowed to summarize topics. | ||||
|     can_summarize_topics_group = models.ForeignKey( | ||||
|         "UserGroup", on_delete=models.RESTRICT, related_name="+" | ||||
|     ) | ||||
|  | ||||
|     # UserGroup whose members are allowed to create invite link. | ||||
|     create_multiuse_invite_group = models.ForeignKey( | ||||
|         "UserGroup", on_delete=models.RESTRICT, related_name="+" | ||||
| @@ -790,6 +795,13 @@ class Realm(models.Model):  # type: ignore[django-manager-missing] # django-stub | ||||
|             allow_everyone_group=True, | ||||
|             default_group_name=SystemGroups.EVERYONE, | ||||
|         ), | ||||
|         can_summarize_topics_group=GroupPermissionSetting( | ||||
|             require_system_group=False, | ||||
|             allow_internet_group=False, | ||||
|             allow_nobody_group=True, | ||||
|             allow_everyone_group=True, | ||||
|             default_group_name=SystemGroups.EVERYONE, | ||||
|         ), | ||||
|         direct_message_initiator_group=GroupPermissionSetting( | ||||
|             require_system_group=False, | ||||
|             allow_internet_group=False, | ||||
| @@ -1203,6 +1215,8 @@ def get_realm_with_settings(realm_id: int) -> Realm: | ||||
|         "can_move_messages_between_channels_group__named_user_group", | ||||
|         "can_move_messages_between_topics_group", | ||||
|         "can_move_messages_between_topics_group__named_user_group", | ||||
|         "can_summarize_topics_group", | ||||
|         "can_summarize_topics_group__named_user_group", | ||||
|         "direct_message_initiator_group", | ||||
|         "direct_message_initiator_group__named_user_group", | ||||
|         "direct_message_permission_group", | ||||
|   | ||||
| @@ -873,6 +873,9 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): | ||||
|     def can_delete_own_message(self) -> bool: | ||||
|         return self.has_permission("can_delete_own_message_group") | ||||
|  | ||||
|     def can_summarize_topics(self) -> bool: | ||||
|         return self.has_permission("can_summarize_topics_group") | ||||
|  | ||||
|     def can_access_public_streams(self) -> bool: | ||||
|         return not (self.is_guest or self.realm.is_zephyr_mirror_realm) | ||||
|  | ||||
|   | ||||
| @@ -4647,6 +4647,14 @@ paths: | ||||
|                                               create and edit user groups. | ||||
| 
 | ||||
|                                             [calc-full-member]: /api/roles-and-permissions#determining-if-a-user-is-a-full-member | ||||
|                                     can_summarize_topics_group: | ||||
|                                       allOf: | ||||
|                                         - $ref: "#/components/schemas/GroupSettingValue" | ||||
|                                         - description: | | ||||
|                                             A [group-setting value](/api/group-setting-values) defining the | ||||
|                                             set of users who are allowed to use AI summarization. | ||||
| 
 | ||||
|                                             **Changes**: New in Zulip 10.0 (feature level 350). | ||||
|                                     create_multiuse_invite_group: | ||||
|                                       allOf: | ||||
|                                         - $ref: "#/components/schemas/GroupSettingValue" | ||||
| @@ -17662,6 +17670,14 @@ paths: | ||||
|                               to be of type integer and did not accept anonymous user groups. | ||||
| 
 | ||||
|                               New in Zulip 8.0 (feature level 225). | ||||
|                       realm_can_summarize_topics_group: | ||||
|                         allOf: | ||||
|                           - $ref: "#/components/schemas/GroupSettingValue" | ||||
|                           - description: | | ||||
|                               A [group-setting value](/api/group-setting-values) defining the | ||||
|                               set of users who are allowed to use AI summarization. | ||||
| 
 | ||||
|                               **Changes**: New in Zulip 10.0 (feature level 350). | ||||
|                       zulip_plan_is_not_limited: | ||||
|                         type: boolean | ||||
|                         description: | | ||||
|   | ||||
| @@ -142,6 +142,7 @@ class HomeTest(ZulipTestCase): | ||||
|         "realm_can_manage_all_groups", | ||||
|         "realm_can_move_messages_between_channels_group", | ||||
|         "realm_can_move_messages_between_topics_group", | ||||
|         "realm_can_summarize_topics_group", | ||||
|         "realm_create_multiuse_invite_group", | ||||
|         "realm_create_private_stream_policy", | ||||
|         "realm_create_public_stream_policy", | ||||
|   | ||||
| @@ -7,7 +7,11 @@ from django.conf import settings | ||||
| from typing_extensions import override | ||||
|  | ||||
| from analytics.models import UserCount | ||||
| from zerver.actions.realm_settings import do_change_realm_permission_group_setting | ||||
| from zerver.lib.test_classes import ZulipTestCase | ||||
| from zerver.models import NamedUserGroup | ||||
| from zerver.models.groups import SystemGroups | ||||
| from zerver.models.realms import get_realm | ||||
|  | ||||
| warnings.filterwarnings("ignore", category=UserWarning, module="pydantic") | ||||
| warnings.filterwarnings("ignore", category=DeprecationWarning, module="pydantic") | ||||
| @@ -119,3 +123,82 @@ class MessagesSummaryTestCase(ZulipTestCase): | ||||
|         ): | ||||
|             response = self.client_get("/json/messages/summary") | ||||
|             self.assert_json_error_contains(response, "Reached monthly limit for AI credits.") | ||||
|  | ||||
|     def test_permission_to_summarize_message_in_topics(self) -> None: | ||||
|         narrow = orjson.dumps([["channel", self.channel_name], ["topic", self.topic_name]]).decode() | ||||
|  | ||||
|         realm = get_realm("zulip") | ||||
|         moderators_group = NamedUserGroup.objects.get( | ||||
|             name=SystemGroups.MODERATORS, realm=realm, is_system_group=True | ||||
|         ) | ||||
|  | ||||
|         do_change_realm_permission_group_setting( | ||||
|             realm, | ||||
|             "can_summarize_topics_group", | ||||
|             moderators_group, | ||||
|             acting_user=None, | ||||
|         ) | ||||
|  | ||||
|         # In this code path, we test using the fixtures. | ||||
|         with open(LLM_FIXTURES_FILE, "rb") as f: | ||||
|             fixture_data = orjson.loads(f.read()) | ||||
|  | ||||
|         def check_message_summary_permission(user: str, expect_fail: bool = False) -> None: | ||||
|             self.login(user) | ||||
|             with ( | ||||
|                 self.settings( | ||||
|                     TOPIC_SUMMARIZATION_MODEL="groq/llama-3.3-70b-versatile", | ||||
|                     TOPIC_SUMMARIZATION_API_KEY="test", | ||||
|                 ), | ||||
|                 mock.patch("litellm.completion", return_value=fixture_data["response"]), | ||||
|             ): | ||||
|                 result = self.client_get("/json/messages/summary", dict(narrow=narrow)) | ||||
|  | ||||
|             if expect_fail: | ||||
|                 self.assert_json_error(result, "Insufficient permission") | ||||
|             else: | ||||
|                 self.assert_json_success(result) | ||||
|  | ||||
|         check_message_summary_permission("hamlet", expect_fail=True) | ||||
|         check_message_summary_permission("shiva") | ||||
|  | ||||
|         nobody_group = NamedUserGroup.objects.get( | ||||
|             name=SystemGroups.NOBODY, realm=realm, is_system_group=True | ||||
|         ) | ||||
|         do_change_realm_permission_group_setting( | ||||
|             realm, | ||||
|             "can_summarize_topics_group", | ||||
|             nobody_group, | ||||
|             acting_user=None, | ||||
|         ) | ||||
|  | ||||
|         check_message_summary_permission("desdemona", expect_fail=True) | ||||
|  | ||||
|         hamletcharacters_group = NamedUserGroup.objects.get(name="hamletcharacters", realm=realm) | ||||
|         do_change_realm_permission_group_setting( | ||||
|             realm, | ||||
|             "can_summarize_topics_group", | ||||
|             hamletcharacters_group, | ||||
|             acting_user=None, | ||||
|         ) | ||||
|  | ||||
|         check_message_summary_permission("desdemona", expect_fail=True) | ||||
|         check_message_summary_permission("othello", expect_fail=True) | ||||
|         check_message_summary_permission("hamlet") | ||||
|         check_message_summary_permission("cordelia") | ||||
|  | ||||
|         setting_group = self.create_or_update_anonymous_group_for_setting( | ||||
|             [self.example_user("othello")], [moderators_group] | ||||
|         ) | ||||
|         do_change_realm_permission_group_setting( | ||||
|             realm, | ||||
|             "can_summarize_topics_group", | ||||
|             setting_group, | ||||
|             acting_user=None, | ||||
|         ) | ||||
|  | ||||
|         check_message_summary_permission("cordelia", expect_fail=True) | ||||
|         check_message_summary_permission("hamlet", expect_fail=True) | ||||
|         check_message_summary_permission("othello") | ||||
|         check_message_summary_permission("shiva") | ||||
|         check_message_summary_permission("desdemona") | ||||
|   | ||||
| @@ -27,8 +27,8 @@ def get_messages_summary( | ||||
|     if settings.TOPIC_SUMMARIZATION_MODEL is None:  # nocoverage | ||||
|         raise JsonableError(_("AI features are not enabled on this server.")) | ||||
|  | ||||
|     if not (user_profile.is_moderator or user_profile.is_realm_admin):  # nocoverage | ||||
|         return json_success(request, {"summary": "Feature limited to moderators for now."}) | ||||
|     if not user_profile.can_summarize_topics(): | ||||
|         raise JsonableError(_("Insufficient permission")) | ||||
|  | ||||
|     if settings.MAX_PER_USER_MONTHLY_AI_COST is not None: | ||||
|         used_credits = COUNT_STATS["ai_credit_usage::day"].current_month_accumulated_count_for_user( | ||||
|   | ||||
| @@ -136,6 +136,7 @@ def update_realm( | ||||
|     can_manage_all_groups: Json[GroupSettingChangeRequest] | None = None, | ||||
|     can_move_messages_between_channels_group: Json[GroupSettingChangeRequest] | None = None, | ||||
|     can_move_messages_between_topics_group: Json[GroupSettingChangeRequest] | None = None, | ||||
|     can_summarize_topics_group: Json[GroupSettingChangeRequest] | None = None, | ||||
|     direct_message_initiator_group: Json[GroupSettingChangeRequest] | None = None, | ||||
|     direct_message_permission_group: Json[GroupSettingChangeRequest] | None = None, | ||||
|     wildcard_mention_policy: Json[WildcardMentionPolicyEnum] | None = None, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user