diff --git a/proto/api/v1/workspace_service.proto b/proto/api/v1/workspace_service.proto index fc30aee2a..baac3567b 100644 --- a/proto/api/v1/workspace_service.proto +++ b/proto/api/v1/workspace_service.proto @@ -169,6 +169,9 @@ message WorkspaceSetting { bool enable_blur_nsfw_content = 9; // nsfw_tags is the list of tags that mark content as NSFW for blurring. repeated string nsfw_tags = 10; + // use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3. + // When false, images stored in S3 will not have thumbnails generated. + bool use_thumbnails_for_s3_images = 11; } } diff --git a/proto/gen/api/v1/user_service.pb.go b/proto/gen/api/v1/user_service.pb.go index 2067416b6..332ec9387 100644 --- a/proto/gen/api/v1/user_service.pb.go +++ b/proto/gen/api/v1/user_service.pb.go @@ -428,7 +428,6 @@ type GetUserRequest struct { // Supports both numeric IDs and username strings: // - users/{id} (e.g., users/101) // - users/{username} (e.g., users/steven) - // // Format: users/{id_or_username} Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Optional. The fields to return in the response. diff --git a/proto/gen/api/v1/workspace_service.pb.go b/proto/gen/api/v1/workspace_service.pb.go index e1e9ef4a0..28c791887 100644 --- a/proto/gen/api/v1/workspace_service.pb.go +++ b/proto/gen/api/v1/workspace_service.pb.go @@ -672,9 +672,12 @@ type WorkspaceSetting_MemoRelatedSetting struct { // enable_blur_nsfw_content enables blurring of content marked as not safe for work (NSFW). EnableBlurNsfwContent bool `protobuf:"varint,9,opt,name=enable_blur_nsfw_content,json=enableBlurNsfwContent,proto3" json:"enable_blur_nsfw_content,omitempty"` // nsfw_tags is the list of tags that mark content as NSFW for blurring. - NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"` + // use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3. + // When false, images stored in S3 will not have thumbnails generated. + UseThumbnailsForS3Images bool `protobuf:"varint,11,opt,name=use_thumbnails_for_s3_images,json=useThumbnailsForS3Images,proto3" json:"use_thumbnails_for_s3_images,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *WorkspaceSetting_MemoRelatedSetting) Reset() { @@ -770,6 +773,13 @@ func (x *WorkspaceSetting_MemoRelatedSetting) GetNsfwTags() []string { return nil } +func (x *WorkspaceSetting_MemoRelatedSetting) GetUseThumbnailsForS3Images() bool { + if x != nil { + return x.UseThumbnailsForS3Images + } + return false +} + // Custom profile configuration for workspace branding. type WorkspaceSetting_GeneralSetting_CustomProfile struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -935,7 +945,7 @@ const file_api_v1_workspace_service_proto_rawDesc = "" + "\aversion\x18\x02 \x01(\tR\aversion\x12\x12\n" + "\x04mode\x18\x03 \x01(\tR\x04mode\x12!\n" + "\finstance_url\x18\x06 \x01(\tR\vinstanceUrl\"\x1c\n" + - "\x1aGetWorkspaceProfileRequest\"\x97\x11\n" + + "\x1aGetWorkspaceProfileRequest\"\xd7\x11\n" + "\x10WorkspaceSetting\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12X\n" + "\x0fgeneral_setting\x18\x02 \x01(\v2-.memos.api.v1.WorkspaceSetting.GeneralSettingH\x00R\x0egeneralSetting\x12X\n" + @@ -972,7 +982,7 @@ const file_api_v1_workspace_service_proto_rawDesc = "" + "\x18STORAGE_TYPE_UNSPECIFIED\x10\x00\x12\f\n" + "\bDATABASE\x10\x01\x12\t\n" + "\x05LOCAL\x10\x02\x12\x06\n" + - "\x02S3\x10\x03\x1a\xd8\x03\n" + + "\x02S3\x10\x03\x1a\x98\x04\n" + "\x12MemoRelatedSetting\x12<\n" + "\x1adisallow_public_visibility\x18\x01 \x01(\bR\x18disallowPublicVisibility\x127\n" + "\x18display_with_update_time\x18\x02 \x01(\bR\x15displayWithUpdateTime\x120\n" + @@ -983,7 +993,8 @@ const file_api_v1_workspace_service_proto_rawDesc = "" + "\x1adisable_markdown_shortcuts\x18\b \x01(\bR\x18disableMarkdownShortcuts\x127\n" + "\x18enable_blur_nsfw_content\x18\t \x01(\bR\x15enableBlurNsfwContent\x12\x1b\n" + "\tnsfw_tags\x18\n" + - " \x03(\tR\bnsfwTags\"F\n" + + " \x03(\tR\bnsfwTags\x12>\n" + + "\x1cuse_thumbnails_for_s3_images\x18\v \x01(\bR\x18useThumbnailsForS3Images\"F\n" + "\x03Key\x12\x13\n" + "\x0fKEY_UNSPECIFIED\x10\x00\x12\v\n" + "\aGENERAL\x10\x01\x12\v\n" + diff --git a/proto/gen/openapi.yaml b/proto/gen/openapi.yaml index d0454333e..73ad960b0 100644 --- a/proto/gen/openapi.yaml +++ b/proto/gen/openapi.yaml @@ -78,35 +78,23 @@ paths: parameters: - name: pageSize in: query - description: |- - Optional. The maximum number of attachments to return. - The service may return fewer than this value. - If unspecified, at most 50 attachments will be returned. - The maximum value is 1000; values above 1000 will be coerced to 1000. + description: "Optional. The maximum number of attachments to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 attachments will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000." schema: type: integer format: int32 - name: pageToken in: query - description: |- - Optional. A page token, received from a previous `ListAttachments` call. - Provide this to retrieve the subsequent page. + description: "Optional. A page token, received from a previous `ListAttachments` call.\r\n Provide this to retrieve the subsequent page." schema: type: string - name: filter in: query - description: |- - Optional. Filter to apply to the list results. - Example: "type=image/png" or "filename:*.jpg" - Supported operators: =, !=, <, <=, >, >=, : - Supported fields: filename, type, size, create_time, memo + description: "Optional. Filter to apply to the list results.\r\n Example: \"type=image/png\" or \"filename:*.jpg\"\r\n Supported operators: =, !=, <, <=, >, >=, :\r\n Supported fields: filename, type, size, create_time, memo" schema: type: string - name: orderBy in: query - description: |- - Optional. The order to sort results by. - Example: "create_time desc" or "filename asc" + description: "Optional. The order to sort results by.\r\n Example: \"create_time desc\" or \"filename asc\"" schema: type: string responses: @@ -130,9 +118,7 @@ paths: parameters: - name: attachmentId in: query - description: |- - Optional. The attachment ID to use for this attachment. - If empty, a unique ID will be generated. + description: "Optional. The attachment ID to use for this attachment.\r\n If empty, a unique ID will be generated." schema: type: string requestBody: @@ -516,26 +502,18 @@ paths: parameters: - name: pageSize in: query - description: |- - Optional. The maximum number of memos to return. - The service may return fewer than this value. - If unspecified, at most 50 memos will be returned. - The maximum value is 1000; values above 1000 will be coerced to 1000. + description: "Optional. The maximum number of memos to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 memos will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000." schema: type: integer format: int32 - name: pageToken in: query - description: |- - Optional. A page token, received from a previous `ListMemos` call. - Provide this to retrieve the subsequent page. + description: "Optional. A page token, received from a previous `ListMemos` call.\r\n Provide this to retrieve the subsequent page." schema: type: string - name: state in: query - description: |- - Optional. The state of the memos to list. - Default to `NORMAL`. Set to `ARCHIVED` to list archived memos. + description: "Optional. The state of the memos to list.\r\n Default to `NORMAL`. Set to `ARCHIVED` to list archived memos." schema: enum: - STATE_UNSPECIFIED @@ -545,20 +523,12 @@ paths: format: enum - name: orderBy in: query - description: |- - Optional. The order to sort results by. - Default to "display_time desc". - Supports comma-separated list of fields following AIP-132. - Example: "pinned desc, display_time desc" or "create_time asc" - Supported fields: pinned, display_time, create_time, update_time, name + description: "Optional. The order to sort results by.\r\n Default to \"display_time desc\".\r\n Supports comma-separated list of fields following AIP-132.\r\n Example: \"pinned desc, display_time desc\" or \"create_time asc\"\r\n Supported fields: pinned, display_time, create_time, update_time, name" schema: type: string - name: filter in: query - description: |- - Optional. Filter to apply to the list results. - Filter is a CEL expression to filter memos. - Refer to `Shortcut.filter`. + description: "Optional. Filter to apply to the list results.\r\n Filter is a CEL expression to filter memos.\r\n Refer to `Shortcut.filter`." schema: type: string - name: showDeleted @@ -587,9 +557,7 @@ paths: parameters: - name: memoId in: query - description: |- - Optional. The memo ID to use for this memo. - If empty, a unique ID will be generated. + description: "Optional. The memo ID to use for this memo.\r\n If empty, a unique ID will be generated." schema: type: string - name: validateOnly @@ -636,9 +604,7 @@ paths: type: string - name: readMask in: query - description: |- - Optional. The fields to return in the response. - If not specified, all fields are returned. + description: "Optional. The fields to return in the response.\r\n If not specified, all fields are returned." schema: type: string format: field-mask @@ -2173,9 +2139,7 @@ components: properties: name: type: string - description: |- - The name of the attachment. - Format: attachments/{attachment} + description: "The name of the attachment.\r\n Format: attachments/{attachment}" createTime: readOnly: true type: string @@ -2201,9 +2165,7 @@ components: description: Output only. The size of the attachment in bytes. memo: type: string - description: |- - Optional. The related memo. Refer to `Memo.name`. - Format: memos/{memo} + description: "Optional. The related memo. Refer to `Memo.name`.\r\n Format: memos/{memo}" CreateSessionRequest: type: object properties: @@ -2277,9 +2239,7 @@ components: properties: parent: type: string - description: |- - Required. The parent, who owns the tags. - Format: memos/{memo}. Use "memos/-" to delete all tags. + description: "Required. The parent, who owns the tags.\r\n Format: memos/{memo}. Use \"memos/-\" to delete all tags." tag: type: string description: Required. The tag name to delete. @@ -2440,9 +2400,7 @@ components: description: The list of attachments. nextPageToken: type: string - description: |- - A token that can be sent as `page_token` to retrieve the next page. - If this field is omitted, there are no subsequent pages. + description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages." totalSize: type: integer description: The total count of attachments (may be approximate). @@ -2542,9 +2500,7 @@ components: description: The list of memos. nextPageToken: type: string - description: |- - A token that can be sent as `page_token` to retrieve the next page. - If this field is omitted, there are no subsequent pages. + description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages." totalSize: type: integer description: The total count of memos (may be approximate). @@ -2646,9 +2602,7 @@ components: properties: name: type: string - description: |- - The resource name of the memo. - Format: memos/{memo}, memo is the user defined id or uuid. + description: "The resource name of the memo.\r\n Format: memos/{memo}, memo is the user defined id or uuid." state: enum: - STATE_UNSPECIFIED @@ -2660,9 +2614,7 @@ components: creator: readOnly: true type: string - description: |- - The name of the creator. - Format: users/{user} + description: "The name of the creator.\r\n Format: users/{user}" createTime: readOnly: true type: string @@ -2722,9 +2674,7 @@ components: parent: readOnly: true type: string - description: |- - Output only. The name of the parent memo. - Format: memos/{memo} + description: "Output only. The name of the parent memo.\r\n Format: memos/{memo}" snippet: readOnly: true type: string @@ -2762,9 +2712,7 @@ components: properties: name: type: string - description: |- - The resource name of the memo. - Format: memos/{memo} + description: "The resource name of the memo.\r\n Format: memos/{memo}" snippet: readOnly: true type: string @@ -2810,21 +2758,14 @@ components: name: readOnly: true type: string - description: |- - The resource name of the reaction. - Format: reactions/{reaction} + description: "The resource name of the reaction.\r\n Format: reactions/{reaction}" creator: readOnly: true type: string - description: |- - The resource name of the creator. - Format: users/{user} + description: "The resource name of the creator.\r\n Format: users/{user}" contentId: type: string - description: |- - The resource name of the content. - For memo reactions, this should be the memo's resource name. - Format: memos/{memo} + description: "The resource name of the content.\r\n For memo reactions, this should be the memo's resource name.\r\n Format: memos/{memo}" reactionType: type: string description: "Required. The type of reaction (e.g., \"\U0001F44D\", \"❤️\", \"\U0001F604\")." @@ -2842,9 +2783,7 @@ components: properties: parent: type: string - description: |- - Required. The parent, who owns the tags. - Format: memos/{memo}. Use "memos/-" to rename all tags. + description: "Required. The parent, who owns the tags.\r\n Format: memos/{memo}. Use \"memos/-\" to rename all tags." oldTag: type: string description: Required. The old tag name to rename. @@ -2859,9 +2798,7 @@ components: properties: name: type: string - description: |- - Required. The resource name of the memo. - Format: memos/{memo} + description: "Required. The resource name of the memo.\r\n Format: memos/{memo}" attachments: type: array items: @@ -2875,9 +2812,7 @@ components: properties: name: type: string - description: |- - Required. The resource name of the memo. - Format: memos/{memo} + description: "Required. The resource name of the memo.\r\n Format: memos/{memo}" relations: type: array items: @@ -2930,9 +2865,7 @@ components: type: string usePathStyle: type: boolean - description: |- - S3 configuration for cloud storage backend. - Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/ + description: "S3 configuration for cloud storage backend.\r\n Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/" UpsertMemoReactionRequest: required: - name @@ -2941,9 +2874,7 @@ components: properties: name: type: string - description: |- - Required. The resource name of the memo. - Format: memos/{memo} + description: "Required. The resource name of the memo.\r\n Format: memos/{memo}" reaction: allOf: - $ref: '#/components/schemas/Reaction' @@ -3219,9 +3150,7 @@ components: properties: owner: type: string - description: |- - The name of instance owner. - Format: users/{user} + description: "The name of instance owner.\r\n Format: users/{user}" version: type: string description: Version is the current version of instance. @@ -3237,9 +3166,7 @@ components: properties: name: type: string - description: |- - The name of the workspace setting. - Format: workspace/settings/{setting} + description: "The name of the workspace setting.\r\n Format: workspace/settings/{setting}" generalSetting: $ref: '#/components/schemas/WorkspaceSetting_GeneralSetting' storageSetting: @@ -3252,9 +3179,7 @@ components: properties: theme: type: string - description: |- - theme is the name of the selected theme. - This references a CSS file in the web/public/themes/ directory. + description: "theme is the name of the selected theme.\r\n This references a CSS file in the web/public/themes/ directory." disallowUserRegistration: type: boolean description: disallow_user_registration disallows user registration. @@ -3273,10 +3198,7 @@ components: description: custom_profile is the custom profile. weekStartDayOffset: type: integer - description: |- - week_start_day_offset is the week start day offset from Sunday. - 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday - Default is Sunday. + description: "week_start_day_offset is the week start day offset from Sunday.\r\n 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday\r\n Default is Sunday." format: int32 disallowChangeUsername: type: boolean @@ -3320,6 +3242,9 @@ components: items: type: string description: nsfw_tags is the list of tags that mark content as NSFW for blurring. + useThumbnailsForS3Images: + type: boolean + description: "use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.\r\n When false, images stored in S3 will not have thumbnails generated." description: Memo-related workspace settings and policies. WorkspaceSetting_StorageSetting: type: object @@ -3335,9 +3260,7 @@ components: format: enum filepathTemplate: type: string - description: |- - The template of file path. - e.g. assets/{timestamp}_{filename} + description: "The template of file path.\r\n e.g. assets/{timestamp}_{filename}" uploadSizeLimitMb: type: string description: The max upload size in megabytes. diff --git a/proto/gen/store/workspace_setting.pb.go b/proto/gen/store/workspace_setting.pb.go index 5c09483f5..833c77a5f 100644 --- a/proto/gen/store/workspace_setting.pb.go +++ b/proto/gen/store/workspace_setting.pb.go @@ -676,9 +676,12 @@ type WorkspaceMemoRelatedSetting struct { // enable_blur_nsfw_content enables blurring of content marked as not safe for work (NSFW). EnableBlurNsfwContent bool `protobuf:"varint,9,opt,name=enable_blur_nsfw_content,json=enableBlurNsfwContent,proto3" json:"enable_blur_nsfw_content,omitempty"` // nsfw_tags is the list of tags that mark content as NSFW for blurring. - NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"` + // use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3. + // When false, images stored in S3 will not have thumbnails generated. + UseThumbnailsForS3Images bool `protobuf:"varint,11,opt,name=use_thumbnails_for_s3_images,json=useThumbnailsForS3Images,proto3" json:"use_thumbnails_for_s3_images,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *WorkspaceMemoRelatedSetting) Reset() { @@ -774,6 +777,13 @@ func (x *WorkspaceMemoRelatedSetting) GetNsfwTags() []string { return nil } +func (x *WorkspaceMemoRelatedSetting) GetUseThumbnailsForS3Images() bool { + if x != nil { + return x.UseThumbnailsForS3Images + } + return false +} + var File_store_workspace_setting_proto protoreflect.FileDescriptor const file_store_workspace_setting_proto_rawDesc = "" + @@ -821,7 +831,7 @@ const file_store_workspace_setting_proto_rawDesc = "" + "\bendpoint\x18\x03 \x01(\tR\bendpoint\x12\x16\n" + "\x06region\x18\x04 \x01(\tR\x06region\x12\x16\n" + "\x06bucket\x18\x05 \x01(\tR\x06bucket\x12$\n" + - "\x0euse_path_style\x18\x06 \x01(\bR\fusePathStyle\"\xe1\x03\n" + + "\x0euse_path_style\x18\x06 \x01(\bR\fusePathStyle\"\xa1\x04\n" + "\x1bWorkspaceMemoRelatedSetting\x12<\n" + "\x1adisallow_public_visibility\x18\x01 \x01(\bR\x18disallowPublicVisibility\x127\n" + "\x18display_with_update_time\x18\x02 \x01(\bR\x15displayWithUpdateTime\x120\n" + @@ -832,7 +842,8 @@ const file_store_workspace_setting_proto_rawDesc = "" + "\x1adisable_markdown_shortcuts\x18\b \x01(\bR\x18disableMarkdownShortcuts\x127\n" + "\x18enable_blur_nsfw_content\x18\t \x01(\bR\x15enableBlurNsfwContent\x12\x1b\n" + "\tnsfw_tags\x18\n" + - " \x03(\tR\bnsfwTags*s\n" + + " \x03(\tR\bnsfwTags\x12>\n" + + "\x1cuse_thumbnails_for_s3_images\x18\v \x01(\bR\x18useThumbnailsForS3Images*s\n" + "\x13WorkspaceSettingKey\x12%\n" + "!WORKSPACE_SETTING_KEY_UNSPECIFIED\x10\x00\x12\t\n" + "\x05BASIC\x10\x01\x12\v\n" + diff --git a/proto/store/workspace_setting.proto b/proto/store/workspace_setting.proto index eb86aba5f..ed6af89c2 100644 --- a/proto/store/workspace_setting.proto +++ b/proto/store/workspace_setting.proto @@ -114,4 +114,7 @@ message WorkspaceMemoRelatedSetting { bool enable_blur_nsfw_content = 9; // nsfw_tags is the list of tags that mark content as NSFW for blurring. repeated string nsfw_tags = 10; + // use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3. + // When false, images stored in S3 will not have thumbnails generated. + bool use_thumbnails_for_s3_images = 11; } diff --git a/server/router/api/v1/attachment_service.go b/server/router/api/v1/attachment_service.go index 64f342904..f87d8f6fa 100644 --- a/server/router/api/v1/attachment_service.go +++ b/server/router/api/v1/attachment_service.go @@ -232,16 +232,29 @@ func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.Ge } if request.Thumbnail && util.HasPrefixes(attachment.Type, SupportedThumbnailMimeTypes...) { - thumbnailBlob, err := s.getOrGenerateThumbnail(attachment) - if err != nil { - // thumbnail failures are logged as warnings and not cosidered critical failures as - // a attachment image can be used in its place. - slog.Warn("failed to get attachment thumbnail image", slog.Any("error", err)) - } else { - return &httpbody.HttpBody{ - ContentType: attachment.Type, - Data: thumbnailBlob, - }, nil + // Check if we should generate thumbnails for S3 images + shouldGenerateThumbnail := true + if attachment.StorageType == storepb.AttachmentStorageType_S3 { + memoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx) + if err != nil { + slog.Warn("failed to get workspace memo related setting", slog.Any("error", err)) + } else if !memoRelatedSetting.UseThumbnailsForS3Images { + shouldGenerateThumbnail = false + } + } + + if shouldGenerateThumbnail { + thumbnailBlob, err := s.getOrGenerateThumbnail(attachment) + if err != nil { + // thumbnail failures are logged as warnings and not cosidered critical failures as + // a attachment image can be used in its place. + slog.Warn("failed to get attachment thumbnail image", slog.Any("error", err)) + } else { + return &httpbody.HttpBody{ + ContentType: attachment.Type, + Data: thumbnailBlob, + }, nil + } } } diff --git a/server/router/api/v1/workspace_service.go b/server/router/api/v1/workspace_service.go index 0af794c62..aaf828120 100644 --- a/server/router/api/v1/workspace_service.go +++ b/server/router/api/v1/workspace_service.go @@ -264,6 +264,7 @@ func convertWorkspaceMemoRelatedSettingFromStore(setting *storepb.WorkspaceMemoR DisableMarkdownShortcuts: setting.DisableMarkdownShortcuts, EnableBlurNsfwContent: setting.EnableBlurNsfwContent, NsfwTags: setting.NsfwTags, + UseThumbnailsForS3Images: setting.UseThumbnailsForS3Images, } } @@ -281,6 +282,7 @@ func convertWorkspaceMemoRelatedSettingToStore(setting *v1pb.WorkspaceSetting_Me DisableMarkdownShortcuts: setting.DisableMarkdownShortcuts, EnableBlurNsfwContent: setting.EnableBlurNsfwContent, NsfwTags: setting.NsfwTags, + UseThumbnailsForS3Images: setting.UseThumbnailsForS3Images, } } diff --git a/web/src/components/Settings/MemoRelatedSettings.tsx b/web/src/components/Settings/MemoRelatedSettings.tsx index 58f9938fc..025759b29 100644 --- a/web/src/components/Settings/MemoRelatedSettings.tsx +++ b/web/src/components/Settings/MemoRelatedSettings.tsx @@ -175,6 +175,13 @@ const MemoRelatedSettings = observer(() => { +