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
@@ -1,7 +1,6 @@
package com.homme.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -14,7 +13,6 @@ import com.homme.demo.dto.ChatRequest;
import com.homme.demo.service.RagService;
@CrossOrigin(origins = "${homme.cors.allowed-origins}")
@RestController
@RequestMapping("/chat")
public class ChatController {
@@ -1,10 +1,14 @@
package com.homme.demo.controller;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.homme.demo.service.WordPressIngestService;
@@ -25,4 +29,5 @@ public class IngestController {
}
}
@@ -2,25 +2,36 @@ package com.homme.demo.controller;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import com.homme.demo.dto.ApiResponse;
import com.homme.demo.dto.ContributionRequest;
import com.homme.demo.dto.FileProcessedDto;
import com.homme.demo.dto.UploadWordFileResponseDto;
import com.homme.demo.entity.Contribution;
import com.homme.demo.input.ContentInput;
import com.homme.demo.repository.ContributionRepository;
import com.homme.demo.service.AzureSpeechService;
import com.homme.demo.service.KnowledgeIngestService;
import com.homme.demo.service.WordExportService;
@@ -30,46 +41,154 @@ import com.homme.demo.service.WordExportService;
@RequestMapping("/knowledge-base")
public class KnowledgeIngestController {
private static final Set<String> ALLOWED_DOC_EXT = Set.of("docx", "doc", "pdf", "txt");
private static final Set<String> ALLOWED_AUDIO_EXT = Set.of("webm", "ogg", "mp3", "wav", "m4a", "mp4");
@Autowired
private WordExportService wordExportService;
@Autowired
private KnowledgeIngestService knowledgeIngestService;
@Autowired
private AzureSpeechService azureSpeechService;
@Autowired
private AzureSpeechService azureSpeechService;
@Autowired
private ContributionRepository contributionRepository;
@Value("${homme.admin.token}")
private String adminToken;
@GetMapping("/build")
public List<ApiResponse<FileProcessedDto>> buildKnowledgeBase() throws Exception {
return knowledgeIngestService.buildKnowledgeBase();
}
@PostMapping("/transcription")
public String transcribe(@RequestParam("file") MultipartFile file) throws IOException{
byte[] audioBytes = file.getBytes();
String fileName = file.getOriginalFilename();
return azureSpeechService.transcribe(audioBytes, fileName);
@PostMapping("/transcription")
public ResponseEntity<Map<String, String>> transcribe(@RequestParam("audio") MultipartFile audio) throws IOException {
String ext = getExtension(audio.getOriginalFilename());
if (!ALLOWED_AUDIO_EXT.contains(ext)) {
return new ResponseEntity<>(Map.of("error", "Nicht unterstütztes Audioformat."), HttpStatus.BAD_REQUEST);
}
String text = azureSpeechService.transcribe(audio.getBytes(), audio.getOriginalFilename());
return ResponseEntity.ok(Map.of("text", text));
}
@PostMapping("/extract")
public ResponseEntity<Map<String, String>> extract(@RequestParam("file") MultipartFile file) throws Exception {
String ext = getExtension(file.getOriginalFilename());
if (!ALLOWED_DOC_EXT.contains(ext)) {
return new ResponseEntity<>(Map.of("error", "Nur DOCX, DOC, PDF oder TXT erlaubt."), HttpStatus.BAD_REQUEST);
}
String text = knowledgeIngestService.extractText(file.getBytes(), file.getOriginalFilename());
return ResponseEntity.ok(Map.of("text", text));
}
@PostMapping(value = "/contributions", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> saveContribution(@RequestBody ContributionRequest request) {
if (request.getConsent() == null || !request.getConsent()) {
return new ResponseEntity<>(Map.of("error", "Einwilligung ist erforderlich."), HttpStatus.BAD_REQUEST);
}
if (request.getName() == null || request.getName().isBlank()) {
return new ResponseEntity<>(Map.of("error", "Name ist erforderlich."), HttpStatus.BAD_REQUEST);
}
if (request.getText() == null || request.getText().isBlank()) {
return new ResponseEntity<>(Map.of("error", "Text darf nicht leer sein."), HttpStatus.BAD_REQUEST);
}
Contribution contribution = Contribution.builder()
.source(request.getSource())
.name(request.getName().trim())
.gender(request.getGender())
.city((request.getCity() == null || request.getCity().isBlank()) ? "Dortmund" : request.getCity().trim())
.text(request.getText())
.consent(true)
.status("PENDING")
.createdAt(LocalDateTime.now())
.build();
Contribution saved = contributionRepository.save(contribution);
return new ResponseEntity<>(Map.of("id", saved.getId(), "status", saved.getStatus()), HttpStatus.CREATED);
}
@GetMapping("/contributions/pending")
public List<Contribution> pendingContributions(@RequestHeader("X-Admin-Token") String token) {
checkAdminToken(token);
return contributionRepository.findByStatus("PENDING");
}
@PostMapping("/contributions/{id}/approve")
public ResponseEntity<ApiResponse<FileProcessedDto>> approveContribution(@PathVariable Long id,
@RequestHeader("X-Admin-Token") String token) {
checkAdminToken(token);
try {
Contribution contribution = contributionRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Beitrag nicht gefunden."));
byte[] docx = wordExportService.createInterviewDocx(contribution.getText());
String fileName = "%s_%s_%s.docx".formatted(
LocalDate.now(),
contribution.getName().replace(" ", "_"),
contribution.getCity());
UploadWordFileResponseDto upload = knowledgeIngestService.uploadWordFile(fileName, docx);
if (!upload.isSuccess()) {
return new ResponseEntity<>(ApiResponse.error("Upload fehlgeschlagen: " + upload.getMessage()),
HttpStatus.BAD_REQUEST);
}
ApiResponse<FileProcessedDto> processed = knowledgeIngestService.processOneFile(upload.getLink());
contribution.setStatus("APPROVED");
contributionRepository.save(contribution);
return new ResponseEntity<>(processed, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(ApiResponse.error("Fehler: " + e.getMessage()),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private void checkAdminToken(String token) {
if (adminToken == null || adminToken.isBlank() || !adminToken.equals(token)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Ungültiger Admin-Token");
}
}
private String getExtension(String fileName) {
if (fileName == null || !fileName.contains(".")) return "";
return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
}
@PostMapping(value = "/save-new-interview", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<ApiResponse<FileProcessedDto>> saveNewInterview(@ModelAttribute ContentInput contentInput ) {
System.out.println("rrrr ");
try {
String rawText;
if (contentInput.getFile() != null && !contentInput.getFile().isEmpty()) {
rawText = wordExportService.leseDatei(contentInput.getFile());
} else if (contentInput.getText() != null && !contentInput.getText().isBlank()) {
rawText = contentInput.getText();
@@ -81,28 +200,28 @@ public class KnowledgeIngestController {
return new ResponseEntity<>(ApiResponse.error("Kein lesbarer Inhalt gefunden."), HttpStatus.BAD_REQUEST);
}
String title = (contentInput.getTitle() == null || contentInput.getTitle().isBlank())
? "Anonymous" : contentInput.getTitle();
String city = (contentInput.getCity() == null|| contentInput.getCity().isBlank())
? "Dortmund" : contentInput.getCity();
byte[] docx = wordExportService.createInterviewDocx(rawText);
String fileName = "%s_%s_%s.docx".formatted(LocalDate.now(),title.trim().replace(" ", "_"),city);
UploadWordFileResponseDto upload = knowledgeIngestService.uploadWordFile(fileName, docx);
if(!upload.isSuccess()) {
return new ResponseEntity<>(ApiResponse.error("Upload fehlgeschlagen: " +upload.getMessage()), HttpStatus.BAD_REQUEST);
}
ApiResponse<FileProcessedDto> processed = knowledgeIngestService.processOneFile(upload.getLink());
return new ResponseEntity<>(processed, HttpStatus.CREATED);
@@ -112,7 +231,3 @@ public class KnowledgeIngestController {
}
}
}