# HÖMMA – Änderungsplan (Backend an Plugin anpassen) خطة التعديل لمطابقة الباك-إند مع إضافة **HÖMMA Human Library**. الفكرة: نضيف **Controller جديد واحد** يوفّر النقاط الثلاث تماماً كما يتوقّعها الـ plugin، ويستدعي الخدمات الموجودة أصلاً. لا نلمس الـ Controllers الشغّالة. الطريقة المختارة: **تعديل الباك-إند** (وليس الـ plugin). النقاط الثلاث النهائية التي ستضعها في ووردبريس: | خانة ووردبريس | الرابط النهائي (مثال Azure) | |---|---| | Speichern-Endpunkt | `https:///human-library` | | Transkriptions-Endpunkt | `https:///transcribe` | | Extraktions-Endpunkt | `https:///extract` | --- ## 1) pom.xml — إضافة دعم PDF الـ plugin يسمح برفع Word **و PDF**، لكن المشروع يعالج `.docx` فقط. أضف مكتبة PDFBox داخل ``: ```xml org.apache.pdfbox pdfbox 2.0.31 ``` --- ## 2) WordExportService.java — تفعيل قراءة PDF الملف: `src/main/java/com/homme/demo/service/WordExportService.java` داخل دالة `leseDatei(MultipartFile file)`، **قبل** سطر `return knowledgeIngestService.decodeBytes(bytes);` أضف فرع PDF: ```java if (name.endsWith(".pdf")) { try (org.apache.pdfbox.pdmodel.PDDocument doc = org.apache.pdfbox.pdmodel.PDDocument.load(bytes)) { return new org.apache.pdfbox.text.PDFTextStripper().getText(doc); } } ``` بذلك: `.docx` عبر POI، `.pdf` عبر PDFBox، والباقي يبقى كما هو. --- ## 3) DTO جديد — طلب الحفظ (JSON) ملف جديد: `src/main/java/com/homme/demo/dto/HumanLibraryRequest.java` الـ plugin يرسل JSON بهذه الحقول: `source, name, gender, city, text, consent`. ```java package com.homme.demo.dto; import lombok.Getter; import lombok.Setter; @Getter @Setter public class HumanLibraryRequest { private String source; private String name; private String gender; private String city; private String text; private boolean consent; } ``` --- ## 4) DTO جديد — الرد الموحّد `{ "text": "..." }` ملف جديد: `src/main/java/com/homme/demo/dto/TextResponse.java` الـ plugin يتوقّع من الاستخراج والتفريغ رداً بصيغة `{"text":"..."}`. ```java package com.homme.demo.dto; public record TextResponse(String text) { } ``` --- ## 5) Controller جديد — النقاط الثلاث للـ plugin ملف جديد: `src/main/java/com/homme/demo/controller/HumanLibraryController.java` هذا هو قلب التعديل. يوفّر `/extract`, `/transcribe`, `/human-library` بنفس أسماء الحقول والصيغ التي يتوقّعها الـ plugin، ويستدعي الخدمات الموجودة. ```java package com.homme.demo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import com.homme.demo.dto.HumanLibraryRequest; import com.homme.demo.dto.TextResponse; import com.homme.demo.dto.UploadWordFileResponseDto; import com.homme.demo.service.AzureSpeechService; import com.homme.demo.service.KnowledgeIngestService; import com.homme.demo.service.WordExportService; import java.time.LocalDate; @RestController public class HumanLibraryController { @Autowired private WordExportService wordExportService; @Autowired private AzureSpeechService azureSpeechService; @Autowired private KnowledgeIngestService knowledgeIngestService; // (1) Extraktion: Word/PDF -> { "text": "..." } @PostMapping(value = "/extract", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity extract(@RequestParam("file") MultipartFile file) throws Exception { if (file == null || file.isEmpty()) { return ResponseEntity.badRequest().body(new TextResponse("")); } String text = wordExportService.leseDatei(file); return ResponseEntity.ok(new TextResponse(text == null ? "" : text)); } // (2) Transkription: audio -> { "text": "..." } @PostMapping(value = "/transcribe", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity transcribe(@RequestParam("audio") MultipartFile audio) throws Exception { if (audio == null || audio.isEmpty()) { return ResponseEntity.badRequest().body(new TextResponse("")); } String text = azureSpeechService.transcribe(audio.getBytes(), audio.getOriginalFilename()); return ResponseEntity.ok(new TextResponse(text == null ? "" : text)); } // (3) Speichern: JSON { source, name, gender, city, text, consent } -> 201 @PostMapping(value = "/human-library", consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity saveContribution(@RequestBody HumanLibraryRequest req) { try { if (!req.isConsent()) { return ResponseEntity.badRequest().body("Einwilligung (consent) erforderlich."); } if (req.getText() == null || req.getText().isBlank()) { return ResponseEntity.badRequest().body("Text erforderlich."); } String title = (req.getName() == null || req.getName().isBlank()) ? "Anonymous" : req.getName().trim().replace(" ", "_"); String city = (req.getCity() == null || req.getCity().isBlank()) ? "Dortmund" : req.getCity(); // Text -> DOCX -> Humbee-Upload -> RAG-Verarbeitung (wie save-new-interview) byte[] docx = wordExportService.createInterviewDocx(req.getText()); String fileName = "%s_%s_%s.docx".formatted(LocalDate.now(), title, city); UploadWordFileResponseDto upload = knowledgeIngestService.uploadWordFile(fileName, docx); if (!upload.isSuccess()) { return ResponseEntity.status(HttpStatus.BAD_GATEWAY) .body("Upload fehlgeschlagen: " + upload.getMessage()); } knowledgeIngestService.processOneFile(upload.getLink()); return ResponseEntity.status(HttpStatus.CREATED).body("{\"status\":\"ok\"}"); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Fehler beim Speichern: " + e.getMessage()); } } } ``` > ملاحظة: هذا يعيد استخدام نفس منطق `save-new-interview` الموجود. > إذا أردت لاحقاً مرحلة **مراجعة تحريرية (ausstehend)** قبل RAG، نضيف حالة/جدول قبل استدعاء `processOneFile`. > حقلا `gender` و `source` يُستقبلان لكن لا يُخزَّنان حالياً — أضِفهما للـ entity لاحقاً إن لزم. --- ## 6) CORS — (اختياري، موصى به) الملف: `src/main/java/com/homme/demo/config/WebConfig.java` حالياً مفتوح للكل (`allowedOriginPatterns("*")`) — يعمل. للحصر على نطاقك فقط (كما نصح صاحب العمل) استبدله بـ: ```java registry.addMapping("/**") .allowedOrigins("https://hmmahumanlibraryruhr1314.live-website.com") .allowedMethods("GET", "POST", "OPTIONS") .allowedHeaders("*") .allowCredentials(false) .maxAge(3600); ``` (اترك `*` إن أردت تبسيط الاختبار الآن.) --- ## 7) Dockerfile — جديد في جذر المشروع ملف جديد: `Dockerfile` (بجانب `pom.xml`) ```dockerfile FROM maven:3.9-eclipse-temurin-17 AS build WORKDIR /app COPY pom.xml . RUN mvn -q dependency:go-offline COPY src ./src RUN mvn -q clean package -DskipTests FROM eclipse-temurin:17-jre WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 9192 ENTRYPOINT ["java", "-jar", "app.jar"] ``` ملف جديد: `.dockerignore` ``` target/ .git/ .settings/ .classpath .project *.iml ``` --- ## 8) متغيّرات البيئة (Azure) — ليست تعديل كود عند الرفع على Azure App Service اضبط: ``` WEBSITES_PORT=9192 DB_PASSWORD=... OPENAI_API_MISTRAL_KEY=... AZURE_EMBEDDING_KEY=... AZURE_SPEECH_KEY=... HUMBEE_PASSWORD=... ``` وفعّل **Managed Identity** للتطبيق (بسبب `DefaultAzureCredential`)، وامنحها صلاحية على مورد `homme-foundry-dev`، واسمح لـ PostgreSQL بالاتصال (Allow Azure services). --- ## ملخّص الملفات | # | الملف | النوع | |---|---|---| | 1 | `pom.xml` | تعديل (dependency) | | 2 | `service/WordExportService.java` | تعديل (فرع PDF) | | 3 | `dto/HumanLibraryRequest.java` | جديد | | 4 | `dto/TextResponse.java` | جديد | | 5 | `controller/HumanLibraryController.java` | جديد | | 6 | `config/WebConfig.java` | تعديل (اختياري) | | 7 | `Dockerfile` + `.dockerignore` | جديد | | 8 | متغيّرات Azure | إعداد (لا كود) | بعد هذه التعديلات: تبني بـ Docker → ترفع على Azure → تضع الروابط الثلاثة في صفحة إعدادات HÖMMA Human Library → يبدأ النموذج يعمل بالكامل (استخراج، تفريغ، حفظ).