ask personalized questions for site creation

This commit is contained in:
Krrish Ghimire
2026-07-26 20:09:08 +05:45
parent 1ad839a013
commit 6139f8221f
7 changed files with 249 additions and 21 deletions

View File

@@ -3,7 +3,8 @@ package com.krrishg.dto;
import java.util.List;
public record SuggestSectionsResponse(
List<SectionSuggestion> sections
List<SectionSuggestion> sections,
List<PersonalizedQuestion> personalizedQuestions
) {
public record SectionSuggestion(
String title,
@@ -16,4 +17,11 @@ public record SuggestSectionsResponse(
String title,
String description
) {}
public record PersonalizedQuestion(
String id,
String question,
String type,
List<String> options
) {}
}

View File

@@ -617,12 +617,14 @@ public class LLMService {
}
private SuggestSectionsResponse parseSectionSuggestions(String llmOutput) {
String json = extractJsonArray(llmOutput);
String json = extractJsonObject(llmOutput);
try {
JsonNode root = objectMapper.readTree(json);
List<SuggestSectionsResponse.SectionSuggestion> sections = new ArrayList<>();
if (root.isArray()) {
for (JsonNode node : root) {
List<SuggestSectionsResponse.PersonalizedQuestion> questions = new ArrayList<>();
if (root.has("sections") && root.get("sections").isArray()) {
for (JsonNode node : root.get("sections")) {
String title = node.has("title") ? node.get("title").asText() : null;
String description = node.has("description") ? node.get("description").asText() : null;
String slug = node.has("slug") ? node.get("slug").asText() : null;
@@ -641,15 +643,61 @@ public class LLMService {
}
}
}
if (root.has("personalizedQuestions") && root.get("personalizedQuestions").isArray()) {
for (JsonNode node : root.get("personalizedQuestions")) {
String id = node.has("id") ? node.get("id").asText() : null;
String question = node.has("question") ? node.get("question").asText() : null;
String type = node.has("type") ? node.get("type").asText() : "text";
if (id != null && question != null) {
List<String> options = null;
if (node.has("options") && node.get("options").isArray()) {
options = new ArrayList<>();
for (JsonNode opt : node.get("options")) {
options.add(opt.asText());
}
}
questions.add(new SuggestSectionsResponse.PersonalizedQuestion(id, question, type, options));
}
}
}
if (sections.isEmpty()) {
throw new IllegalArgumentException("LLM output must contain at least one section suggestion");
}
return new SuggestSectionsResponse(sections);
return new SuggestSectionsResponse(sections, questions);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to parse section suggestions as JSON: " + e.getMessage());
}
}
private String extractJsonObject(String raw) {
if (raw == null || raw.isBlank()) {
throw new IllegalArgumentException("LLM output is empty");
}
String trimmed = raw.trim();
if (trimmed.startsWith("```")) {
int start = trimmed.indexOf('\n');
int end = trimmed.lastIndexOf("```");
if (start > 0 && end > start) {
trimmed = trimmed.substring(start, end).trim();
}
}
if (!trimmed.startsWith("{")) {
int brace = trimmed.indexOf('{');
if (brace >= 0) {
trimmed = trimmed.substring(brace);
}
}
if (!trimmed.endsWith("}")) {
int brace = trimmed.lastIndexOf('}');
if (brace >= 0) {
trimmed = trimmed.substring(0, brace + 1);
}
}
return trimmed;
}
private String extractJsonArray(String raw) {
if (raw == null || raw.isBlank()) {
throw new IllegalArgumentException("LLM output is empty");

View File

@@ -380,7 +380,8 @@ class LLMControllerTest {
List.of(
new SuggestSectionsResponse.SectionSuggestion("Home", "Hero section", "home", List.of()),
new SuggestSectionsResponse.SectionSuggestion("About", "Bio", "about", List.of())
)
),
List.of()
);
when(llmService.suggestSections(any(), anyString(), anyString(), any(), any(), any())).thenReturn(response);

View File

@@ -502,7 +502,8 @@ class LLMServiceTest {
@Test
void suggestSectionsReturnsParsedSuggestions() {
geminiProvider.withContent("""
[
{
"sections": [
{"title": "Home", "description": "Hero section", "slug": "home",
"patterns": [
{"title": "Full-screen hero", "description": "Large image with text overlay"},
@@ -513,7 +514,12 @@ class LLMServiceTest {
{"title": "Timeline", "description": "Vertical story timeline"}
]},
{"title": "Gallery", "description": "Portfolio grid", "slug": "gallery", "patterns": []}
]
],
"personalizedQuestions": [
{"id": "name", "question": "What is your name?", "type": "text"},
{"id": "bio", "question": "Write a short bio", "type": "textarea"}
]
}
""");
SuggestSectionsResponse response = llmService.suggestSections(
@@ -527,6 +533,10 @@ class LLMServiceTest {
assertEquals("Full-screen hero", response.sections().get(0).patterns().get(0).title());
assertEquals("Timeline", response.sections().get(1).patterns().get(0).title());
assertEquals(0, response.sections().get(2).patterns().size());
assertEquals(2, response.personalizedQuestions().size());
assertEquals("name", response.personalizedQuestions().get(0).id());
assertEquals("textarea", response.personalizedQuestions().get(1).type());
}
@Test
@@ -540,9 +550,12 @@ class LLMServiceTest {
@Test
void suggestSectionsUsesProvidedApiKey() {
geminiProvider.withContent("""
[
{
"sections": [
{"title": "Home", "description": "Hero", "slug": "home", "patterns": []}
]
],
"personalizedQuestions": []
}
""");
SuggestSectionsResponse response = llmService.suggestSections(