feat: Spring AI Application with Chat, Image Generation, and Web UI#138
feat: Spring AI Application with Chat, Image Generation, and Web UI#138devin-ai-integration[bot] wants to merge 1 commit into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| result.textContent = 'Generating image...'; | ||
| result.className = 'result loading'; | ||
| try { | ||
| const response = await fetch('/api/image/generate', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ prompt }) | ||
| }); | ||
| const data = await response.json(); | ||
| result.textContent = ''; | ||
| result.className = 'result'; | ||
| img.src = data.url; | ||
| img.style.display = 'block'; |
There was a problem hiding this comment.
🔴 textContent assignment destroys <img> child element, making generated images invisible
Setting result.textContent = 'Generating image...' on line 162 removes all child nodes of the #image-result div, including the <img id="generated-image"> element (src/main/resources/templates/index.html:76). The img variable (line 160) still holds a reference to the now-detached DOM element, so img.src = data.url and img.style.display = 'block' on lines 173-174 modify an orphaned element that is no longer in the document. The generated image will never be displayed. This completely breaks the image generation feature on every use.
Fix approach
Instead of using textContent (which replaces all children), either:
- Use a separate
<span>element for the loading text alongside the<img>, or - Re-append the
imgelement after clearing:result.textContent = ''; result.appendChild(img);
| result.textContent = 'Generating image...'; | |
| result.className = 'result loading'; | |
| try { | |
| const response = await fetch('/api/image/generate', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ prompt }) | |
| }); | |
| const data = await response.json(); | |
| result.textContent = ''; | |
| result.className = 'result'; | |
| img.src = data.url; | |
| img.style.display = 'block'; | |
| result.textContent = ''; | |
| result.appendChild(img); | |
| img.style.display = 'none'; | |
| const loadingText = document.createTextNode('Generating image...'); | |
| result.insertBefore(loadingText, img); | |
| result.className = 'result loading'; | |
| try { | |
| const response = await fetch('/api/image/generate', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ prompt }) | |
| }); | |
| const data = await response.json(); | |
| if (loadingText.parentNode) loadingText.remove(); | |
| result.className = 'result'; | |
| img.src = data.url; | |
| img.style.display = 'block'; |
Was this helpful? React with 👍 or 👎 to provide feedback.
| public ChatResponse summarize(@RequestBody Map<String, String> request) { | ||
| String text = request.get("text"); | ||
| return chatService.summarize(text); |
There was a problem hiding this comment.
🔴 Missing null validation on /summarize, /translate, /analyze-code endpoints allows null to propagate to AI API
The summarize, translate, and analyzeCode controller methods extract values from a raw Map<String, String> using request.get("text") / request.get("code") without null checks. If a client sends a request with a missing or misnamed key (e.g., {} or {"txt": "hello"}), the value is null. This null propagates through ChatService.chatWithSystemPrompt() → new UserMessage(null) → chatModel.call(), which will likely throw a NullPointerException or an uninformative 500 error, rather than a proper 400 Bad Request. This contrasts with the chatPost endpoint which properly uses @Valid @RequestBody ChatRequest with @NotBlank validation.
Prompt for agents
The /summarize, /translate, and /analyze-code endpoints in ChatController (lines 43-60) accept Map<String, String> request bodies but never validate that the required keys ("text" for summarize/translate, "code" for analyze-code) are present and non-blank. A missing key causes null to flow through to ChatService.chatWithSystemPrompt and then to new UserMessage(null), resulting in an unhandled NullPointerException (500 error) rather than a 400 Bad Request.
Approach: Either create proper DTO classes (like ChatRequest) with @NotBlank validation for each endpoint, or add null/blank checks in the controller methods before calling the service, throwing a ResponseStatusException(HttpStatus.BAD_REQUEST) if the required field is missing.
Was this helpful? React with 👍 or 👎 to provide feedback.
- Spring Boot 3.4.1 + Spring AI 1.0.0-M5 (OpenAI integration) - Chat completion with streaming, summarization, translation, code analysis - DALL-E 3 image generation - Thymeleaf web UI with tabbed interface - Unit tests for all controllers - Isolated in spring-ai/ subdirectory (independent Maven build)
ca07e6e to
918d2f5
Compare
Summary
Adds a new Spring AI application (Spring Boot 3.4.1 + Spring AI 1.0.0-M5) with OpenAI integration, placed in an isolated
spring-ai/subdirectory to coexist cleanly with the existing RealWorld Gradle app. No existing files are modified.The application provides REST API endpoints for chat completion (with SSE streaming), text summarization, translation, code analysis, and DALL-E 3 image generation. Includes a Thymeleaf-based tabbed web UI and controller-level unit tests (9 tests passing).
Key files added under
spring-ai/:pom.xml— Maven build with Spring AI OpenAI starterChatService/ImageService— AI business logic wrapping Spring AI'sChatClientandImageModelChatController/ImageController— REST endpoints under/api/chatand/api/imageindex.html+app.js+style.css— Browser UI with tabs for each feature@MockitoBeanfor service mockingspring-ai/README.mdwith setup and API documentationThe
spring-ai/directory uses Maven while the root project uses Gradle — these are fully independent build systems with no cross-dependency.Updates since last revision
spring-ai/files. No existing repo files (README.md, .gitignore, application.properties) are touched, which resolves the earlier CI failures caused by the originalapplication.propertiesbeing overwritten with Spring AI config.Review & Testing Checklist for Human
./gradlew clean testwhich only tests the existing RealWorld app. CI passing does not mean the Spring AI code compiles or its tests pass. Manually verify by runningcd spring-ai && mvn clean testwith Java 17+.SPRING_AI_OPENAI_API_KEYenv var, runcd spring-ai && mvn spring-boot:run, openhttp://localhost:8080, and verify chat/summarize/translate/code-analysis/image-generation tabs all produce valid responses from OpenAI.spring-ai 1.0.0-M5is a pre-release milestone. Verify this is acceptable or pin to a stable release if one is available.AiConfigallows all origins (*). Acceptable for dev but should be tightened for any non-local deployment.Notes
app.js) parses SSEdata:prefixes manually; verify this matches the actualtext/event-streamformat emitted by Spring's reactive stack.application.properties).Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/4f744f6357d84af4a7a9f0d4f11df37b