package com.wok.supportbot.app; import com.fasterxml.jackson.annotation.JsonInclude; import org.springframework.ai.document.Document; import java.util.List; import java.util.Map; /** Public citation metadata from the documents actually used by this answer. */ @JsonInclude(JsonInclude.Include.ALWAYS) public record SourceReference(String documentId, String title, String sourceName, Integer chunkIndex, Double score, String snippet) { public SourceReference { if (snippet != null && snippet.length() > 160) { int end = Character.isHighSurrogate(snippet.charAt(158)) ? 158 : 159; snippet = snippet.substring(0, end) + "…"; } } public static List fromDocuments(List documents) { if (documents == null || documents.isEmpty()) { return List.of(); } return documents.stream().map(SourceReference::fromDocument).toList(); } private static SourceReference fromDocument(Document document) { Map metadata = document.getMetadata(); // Preserve the published source endpoint's distance semantics; other retrievers expose score. Object score = metadata.get("distance") != null ? metadata.get("distance") : metadata.get("score"); return new SourceReference(stringValue(metadata.get("documentId")), stringValue(metadata.get("title")), stringValue(metadata.get("sourceName")), integerValue(metadata.get("chunkIndex")), doubleValue(score), document.getText()); } private static String stringValue(Object value) { return value == null ? null : value.toString(); } private static Integer integerValue(Object value) { if (value == null) return null; try { return Integer.valueOf(value.toString()); } catch (NumberFormatException ignored) { return null; } } private static Double doubleValue(Object value) { if (value == null) return null; try { double number = Double.parseDouble(value.toString()); return Double.isFinite(number) ? number : null; } catch (NumberFormatException ignored) { return null; } } }