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