Files
Homme/HOEMMA_AENDERUNGSPLAN.md
2026-07-21 23:43:39 +02:00

9.5 KiB
Raw Permalink Blame History

HÖMMA Änderungsplan (Backend an Plugin anpassen)

خطة التعديل لمطابقة الباك-إند مع إضافة HÖMMA Human Library. الفكرة: نضيف Controller جديد واحد يوفّر النقاط الثلاث تماماً كما يتوقّعها الـ plugin، ويستدعي الخدمات الموجودة أصلاً. لا نلمس الـ Controllers الشغّالة.

الطريقة المختارة: تعديل الباك-إند (وليس الـ plugin). النقاط الثلاث النهائية التي ستضعها في ووردبريس:

خانة ووردبريس الرابط النهائي (مثال Azure)
Speichern-Endpunkt https://<azure-url>/human-library
Transkriptions-Endpunkt https://<azure-url>/transcribe
Extraktions-Endpunkt https://<azure-url>/extract

1) pom.xml — إضافة دعم PDF

الـ plugin يسمح برفع Word و PDF، لكن المشروع يعالج .docx فقط. أضف مكتبة PDFBox داخل <dependencies>:

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.31</version>
</dependency>

2) WordExportService.java — تفعيل قراءة PDF

الملف: src/main/java/com/homme/demo/service/WordExportService.java

داخل دالة leseDatei(MultipartFile file)، قبل سطر return knowledgeIngestService.decodeBytes(bytes); أضف فرع PDF:

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.

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":"..."}.

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، ويستدعي الخدمات الموجودة.

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<TextResponse> 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<TextResponse> 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("*")) — يعمل. للحصر على نطاقك فقط (كما نصح صاحب العمل) استبدله بـ:

registry.addMapping("/**")
        .allowedOrigins("https://hmmahumanlibraryruhr1314.live-website.com")
        .allowedMethods("GET", "POST", "OPTIONS")
        .allowedHeaders("*")
        .allowCredentials(false)
        .maxAge(3600);

(اترك * إن أردت تبسيط الاختبار الآن.)


7) Dockerfile — جديد في جذر المشروع

ملف جديد: Dockerfile (بجانب pom.xml)

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 → يبدأ النموذج يعمل بالكامل (استخراج، تفريغ، حفظ).