2121 decorators ,
2222 exceptions ,
2323 filters ,
24+ metadata ,
2425 mixins ,
2526 pagination ,
2627 status ,
3031 response as drf_response ,
3132)
3233
33- from core import models
34+ from core import enums , models
35+ from core .services .ai_services import AIService
3436
3537from . import permissions , serializers , utils
3638
@@ -302,6 +304,23 @@ def perform_update(self, serializer):
302304 serializer .save ()
303305
304306
307+ class DocumentMetadata (metadata .SimpleMetadata ):
308+ """Custom metadata class to add information"""
309+
310+ def determine_metadata (self , request , view ):
311+ """Add language choices only for the list endpoint."""
312+ simple_metadata = super ().determine_metadata (request , view )
313+
314+ if request .path .endswith ("/documents/" ):
315+ simple_metadata ["actions" ]["POST" ]["language" ] = {
316+ "choices" : [
317+ {"value" : code , "display_name" : name }
318+ for code , name in enums .ALL_LANGUAGES .items ()
319+ ]
320+ }
321+ return simple_metadata
322+
323+
305324class DocumentViewSet (
306325 ResourceViewsetMixin ,
307326 mixins .CreateModelMixin ,
@@ -319,6 +338,7 @@ class DocumentViewSet(
319338 resource_field_name = "document"
320339 queryset = models .Document .objects .all ()
321340 ordering = ["-updated_at" ]
341+ metadata_class = DocumentMetadata
322342
323343 def list (self , request , * args , ** kwargs ):
324344 """Restrict resources returned by the list endpoint"""
@@ -455,10 +475,7 @@ def link_configuration(self, request, *args, **kwargs):
455475 serializer = serializers .LinkDocumentSerializer (
456476 document , data = request .data , partial = True
457477 )
458- if not serializer .is_valid ():
459- return drf_response .Response (
460- serializer .errors , status = status .HTTP_400_BAD_REQUEST
461- )
478+ serializer .is_valid (raise_exception = True )
462479
463480 serializer .save ()
464481 return drf_response .Response (serializer .data , status = status .HTTP_200_OK )
@@ -471,24 +488,21 @@ def attachment_upload(self, request, *args, **kwargs):
471488
472489 # Validate metadata in payload
473490 serializer = serializers .FileUploadSerializer (data = request .data )
474- if not serializer .is_valid ():
475- return drf_response .Response (
476- serializer .errors , status = status .HTTP_400_BAD_REQUEST
477- )
491+ serializer .is_valid (raise_exception = True )
478492
479493 # Generate a generic yet unique filename to store the image in object storage
480494 file_id = uuid .uuid4 ()
481495 extension = serializer .validated_data ["expected_extension" ]
482496 key = f"{ document .key_base } /{ ATTACHMENTS_FOLDER :s} /{ file_id !s} .{ extension :s} "
483497
484498 # Prepare metadata for storage
485- metadata = {"Metadata" : {"owner" : str (request .user .id )}}
499+ extra_args = {"Metadata" : {"owner" : str (request .user .id )}}
486500 if serializer .validated_data ["is_unsafe" ]:
487- metadata ["Metadata" ]["is_unsafe" ] = "true"
501+ extra_args ["Metadata" ]["is_unsafe" ] = "true"
488502
489503 file = serializer .validated_data ["file" ]
490504 default_storage .connection .meta .client .upload_fileobj (
491- file , default_storage .bucket_name , key , ExtraArgs = metadata
505+ file , default_storage .bucket_name , key , ExtraArgs = extra_args
492506 )
493507
494508 return drf_response .Response (
@@ -537,6 +551,63 @@ def retrieve_auth(self, request, *args, **kwargs):
537551 request = utils .generate_s3_authorization_headers (f"{ pk :s} /{ attachment_key :s} " )
538552 return drf_response .Response ("authorized" , headers = request .headers , status = 200 )
539553
554+ @decorators .action (
555+ detail = True ,
556+ methods = ["post" ],
557+ name = "Apply a transformation action on a piece of text with AI" ,
558+ url_path = "ai-transform" ,
559+ throttle_classes = [utils .AIDocumentRateThrottle , utils .AIUserRateThrottle ],
560+ )
561+ def ai_transform (self , request , * args , ** kwargs ):
562+ """
563+ POST /api/v1.0/documents/<resource_id>/ai-transform
564+ with expected data:
565+ - text: str
566+ - action: str [prompt, correct, rephrase, summarize]
567+ Return JSON response with the processed text.
568+ """
569+ # Check permissions first
570+ self .get_object ()
571+
572+ serializer = serializers .AITransformSerializer (data = request .data )
573+ serializer .is_valid (raise_exception = True )
574+
575+ text = serializer .validated_data ["text" ]
576+ action = serializer .validated_data ["action" ]
577+
578+ response = AIService ().transform (text , action )
579+
580+ return drf_response .Response (response , status = status .HTTP_200_OK )
581+
582+ @decorators .action (
583+ detail = True ,
584+ methods = ["post" ],
585+ name = "Translate a piece of text with AI" ,
586+ serializer_class = serializers .AITranslateSerializer ,
587+ url_path = "ai-translate" ,
588+ throttle_classes = [utils .AIDocumentRateThrottle , utils .AIUserRateThrottle ],
589+ )
590+ def ai_translate (self , request , * args , ** kwargs ):
591+ """
592+ POST /api/v1.0/documents/<resource_id>/ai-translate
593+ with expected data:
594+ - text: str
595+ - language: str [settings.LANGUAGES]
596+ Return JSON response with the translated text.
597+ """
598+ # Check permissions first
599+ self .get_object ()
600+
601+ serializer = self .get_serializer (data = request .data )
602+ serializer .is_valid (raise_exception = True )
603+
604+ text = serializer .validated_data ["text" ]
605+ language = serializer .validated_data ["language" ]
606+
607+ response = AIService ().translate (text , language )
608+
609+ return drf_response .Response (response , status = status .HTTP_200_OK )
610+
540611
541612class DocumentAccessViewSet (
542613 ResourceAccessViewsetMixin ,
0 commit comments