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