Skip to content

feat: Spring AI Application with Chat, Image Generation, and Web UI#138

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1776086064-spring-ai-application
Open

feat: Spring AI Application with Chat, Image Generation, and Web UI#138
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1776086064-spring-ai-application

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Apr 13, 2026

Copy link
Copy Markdown

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 starter
  • ChatService / ImageService — AI business logic wrapping Spring AI's ChatClient and ImageModel
  • ChatController / ImageController — REST endpoints under /api/chat and /api/image
  • index.html + app.js + style.css — Browser UI with tabs for each feature
  • Controller tests with @MockitoBean for service mocking
  • Dedicated spring-ai/README.md with setup and API documentation

The 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

  • Squashed previous commits into a single clean commit that only adds spring-ai/ files. No existing repo files (README.md, .gitignore, application.properties) are touched, which resolves the earlier CI failures caused by the original application.properties being overwritten with Spring AI config.

Review & Testing Checklist for Human

  • CI does NOT validate the Spring AI app: The repo CI runs ./gradlew clean test which only tests the existing RealWorld app. CI passing does not mean the Spring AI code compiles or its tests pass. Manually verify by running cd spring-ai && mvn clean test with Java 17+.
  • End-to-end test: Set SPRING_AI_OPENAI_API_KEY env var, run cd spring-ai && mvn spring-boot:run, open http://localhost:8080, and verify chat/summarize/translate/code-analysis/image-generation tabs all produce valid responses from OpenAI.
  • Spring AI milestone version: spring-ai 1.0.0-M5 is a pre-release milestone. Verify this is acceptable or pin to a stable release if one is available.
  • CORS configuration: AiConfig allows all origins (*). Acceptable for dev but should be tightened for any non-local deployment.

Notes

  • Tests mock the AI service layer — they verify controller routing/validation but do not exercise actual OpenAI calls.
  • The streaming JS client (app.js) parses SSE data: prefixes manually; verify this matches the actual text/event-stream format emitted by Spring's reactive stack.
  • Branch history was force-pushed to produce a clean single commit (previous commits had accidentally modified root application.properties).

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/4f744f6357d84af4a7a9f0d4f11df37b


Open with Devin

@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +162 to +174
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';

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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:

  1. Use a separate <span> element for the loading text alongside the <img>, or
  2. Re-append the img element after clearing: result.textContent = ''; result.appendChild(img);
Suggested change
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';
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +44 to +46
public ChatResponse summarize(@RequestBody Map<String, String> request) {
String text = request.get("text");
return chatService.summarize(text);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.
Open in Devin Review

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)
@devin-ai-integration devin-ai-integration Bot force-pushed the devin/1776086064-spring-ai-application branch from ca07e6e to 918d2f5 Compare April 13, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants