Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .envs.example/.local/.django
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,14 @@ REFERENCE_MODEL=llama3.2:3b
# REFERENCE_TIMEOUT=300
# REFERENCE_BATCH_SIZE=10
# REFERENCE_NUM_CTX=8192
# REFERENCE_KEEP_ALIVE=-1

# Front (Llama via HTTP — serviço ollama no local.yml)
# ------------------------------------------------------------------------------
FRONT_ENABLED=true
FRONT_URL=http://ollama:11434
FRONT_MODEL=llama3.2:3b
# FRONT_TOKEN=
# FRONT_TIMEOUT=300
# FRONT_NUM_CTX=8192
# FRONT_KEEP_ALIVE=-1
11 changes: 11 additions & 0 deletions .envs.example/.production/.django
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,14 @@ REFERENCE_MODEL=llama3.2:3b
# REFERENCE_TIMEOUT=300
# REFERENCE_BATCH_SIZE=10
# REFERENCE_NUM_CTX=8192
# REFERENCE_KEEP_ALIVE=-1

# Front (Llama via HTTP — Ollama-compatible API)
# ------------------------------------------------------------------------------
FRONT_ENABLED=true
FRONT_URL=
FRONT_MODEL=llama3.2:3b
# FRONT_TOKEN=
# FRONT_TIMEOUT=300
# FRONT_NUM_CTX=8192
# FRONT_KEEP_ALIVE=-1
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ make down # para os containers
| `core/` | Modelos base, Wagtail home, templates e static |
| `core_settings/` | Configurações editáveis do site (nome, logo, favicon) |
| `users/` | `CustomUser` (`AUTH_USER_MODEL`) |
| `front/` | Marcação do front do artigo (API REST, Llama local, XML SPS 1.10) |
| `compose/` | Dockerfiles e scripts de inicialização |
| `requirements/` | Dependências Python (base, local, production) |

Expand Down
2 changes: 2 additions & 0 deletions config/api_router.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.conf import settings
from rest_framework.routers import DefaultRouter, SimpleRouter

from front.api.v1.views import FrontViewSet
from reference.api.v1.views import ReferenceViewSet

if settings.DEBUG:
Expand All @@ -9,5 +10,6 @@
router = SimpleRouter()

router.register("reference", ReferenceViewSet, basename="reference")
router.register("front", FrontViewSet, basename="front")

urlpatterns = router.urls
10 changes: 10 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"core_settings",
"xml_manager",
"reference",
"front",
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS + WAGTAIL
Expand Down Expand Up @@ -325,3 +326,12 @@
REFERENCE_TOKEN = env("REFERENCE_TOKEN", default="")
REFERENCE_BATCH_SIZE = env.int("REFERENCE_BATCH_SIZE", default=10)
REFERENCE_NUM_CTX = env.int("REFERENCE_NUM_CTX", default=8192)
REFERENCE_KEEP_ALIVE = env("REFERENCE_KEEP_ALIVE", default="-1")

FRONT_ENABLED = env.bool("FRONT_ENABLED", default=True)
FRONT_URL = env("FRONT_URL", default="")
FRONT_MODEL = env("FRONT_MODEL", default="llama3.2:3b")
FRONT_TIMEOUT = env.int("FRONT_TIMEOUT", default=300)
FRONT_TOKEN = env("FRONT_TOKEN", default="")
FRONT_NUM_CTX = env.int("FRONT_NUM_CTX", default=8192)
FRONT_KEEP_ALIVE = env("FRONT_KEEP_ALIVE", default="-1")
Empty file added front/__init__.py
Empty file.
Empty file added front/api/__init__.py
Empty file.
Empty file added front/api/v1/__init__.py
Empty file.
47 changes: 47 additions & 0 deletions front/api/v1/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from rest_framework import serializers

OUTPUT_TYPE_CHOICES = ["json", "xml"]


class FrontMarkRequestSerializer(serializers.Serializer):
front = serializers.CharField(
allow_blank=False,
trim_whitespace=True,
help_text="Texto do front do artigo a marcar.",
)
type = serializers.ChoiceField(
choices=OUTPUT_TYPE_CHOICES,
default="json",
required=False,
help_text="Formato de saída: json ou xml.",
)
language = serializers.CharField(
required=False,
allow_blank=True,
help_text="Idioma principal do artigo (ex.: pt, en, es).",
)


class FrontDocxRequestSerializer(serializers.Serializer):
file = serializers.FileField(
help_text="Arquivo .docx com o front do artigo.",
)
type = serializers.ChoiceField(
choices=OUTPUT_TYPE_CHOICES,
default="json",
required=False,
help_text="Formato de saída: json ou xml.",
)
language = serializers.CharField(
required=False,
allow_blank=True,
help_text="Idioma principal do artigo (ex.: pt, en, es).",
)

def validate_file(self, value):
name = (getattr(value, "name", "") or "").lower()
if not name.endswith(".docx"):
raise serializers.ValidationError("Only .docx files are accepted.")
if getattr(value, "size", None) == 0:
raise serializers.ValidationError("Empty file.")
return value
96 changes: 96 additions & 0 deletions front/api/v1/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from collections.abc import Mapping

from django.http import JsonResponse
from rest_framework.decorators import action
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet

from front.api.v1.serializers import (
FrontDocxRequestSerializer,
FrontMarkRequestSerializer,
)
from front.data_utils import resolve_front_result
from front.exceptions import (
FrontDocxError,
FrontLlamaDisabledError,
FrontLlamaMisconfiguredError,
FrontLlamaUnavailableError,
)
from front.utils import front_from_docx_upload


class FrontViewSet(GenericViewSet):
serializer_class = FrontMarkRequestSerializer
permission_classes = [IsAuthenticated]
http_method_names = [
"get",
"post",
"head",
"options",
]

def get_serializer_class(self):
if getattr(self, "action", None) == "docx":
return FrontDocxRequestSerializer
return FrontMarkRequestSerializer

def create(self, request, *args, **kwargs):
data = request.data
if not isinstance(data, Mapping):
return JsonResponse({"error": "Error processing"}, status=400)

serializer = self.get_serializer(data=data)
if not serializer.is_valid():
return JsonResponse(serializer.errors, status=400)

return self.mark_and_respond(
serializer.validated_data["front"],
serializer.validated_data.get("type", "json"),
serializer.validated_data.get("language") or None,
)

@action(
detail=False,
methods=["get", "post"],
url_path="docx",
parser_classes=[MultiPartParser, FormParser],
)
def docx(self, request):
if request.method == "GET":
return Response({})

serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return JsonResponse(serializer.errors, status=400)

uploaded = serializer.validated_data["file"]
output_type = serializer.validated_data.get("type", "json")
language = serializer.validated_data.get("language") or None
try:
front_text = front_from_docx_upload(uploaded)
except FrontDocxError as exc:
return JsonResponse({"error": str(exc)}, status=400)
return self.mark_and_respond(front_text, output_type, language)

def mark_and_respond(self, front_text, output_type, language):
if not str(front_text or "").strip():
return JsonResponse({"error": "No front provided"}, status=400)
try:
result = resolve_front_result(
front_text,
user=self.request.user,
output_type=output_type,
language=language,
)
except (
FrontLlamaDisabledError,
FrontLlamaMisconfiguredError,
FrontLlamaUnavailableError,
) as exc:
return JsonResponse(
{"error": f"Llama model is not available: {exc}"},
status=503,
)
return JsonResponse(result)
6 changes: 6 additions & 0 deletions front/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class FrontConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "front"
Loading