Human-Library-Endpunkte

This commit is contained in:
Mohammad Zwaib
2026-07-21 23:43:39 +02:00
parent 825b3a371f
commit 65c507f667
17 changed files with 607 additions and 56 deletions
+273
View File
@@ -0,0 +1,273 @@
# 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>`:
```xml
<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:
```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<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("*")`) — يعمل.
للحصر على نطاقك فقط (كما نصح صاحب العمل) استبدله بـ:
```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 → يبدأ النموذج يعمل بالكامل (استخراج، تفريغ، حفظ).