Add chat-ui-htmx module: streaming chat page on Spring AI 2.0.2 with htmx and its SSE extension (vendored), MessageChatMemoryAdvisor keyed by the HTTP session, a Stop button, and the traps measured: EventSource strips one leading space per data line, model text must be HTML-escaped, a cancelled stream left the upstream connection open in 7 of 8 runs until an OkHttp interceptor calls Call.cancel (8 of 8 closed after), and the SDK push source ignores back-pressure (limitRate changes only the demand; a bounded buffer works). The model is a stub OpenAI-compatible SSE server written for the module; no real provider was run.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X
This commit is contained in:
2026-10-31 04:02:56 +00:00
co-authored by Claude Sonnet 5.5
parent c97bcf9adf
commit 68da642f9a
35 changed files with 1571 additions and 0 deletions
+1
View File
@@ -33,5 +33,6 @@ Runnable companion code for the Spring AI articles on [ankurm.com](https://ankur
| [`image-gen/`](image-gen) | `ImageModel` and `ImagePrompt` with `OpenAiImageOptions` (what the real `OpenAiImageModel` puts on the wire, a per-call options object that silently resets the model, a `List<ImageMessage>` prompt that throws), a prompt gate with `ModerationModel` that fails closed, and safe storage in MinIO: sniffed content type, byte and pixel caps, server-chosen keys, a fetcher for provider URLs, presigned reads. A per-image price table from the vendors' pages dated 2026-10-28. No image provider called: the `ImageModel` and `ModerationModel` are hand-written fakes and a mock server; the MinIO tests use a real server through Testcontainers. Spring Boot 4.1.2, Spring AI 2.0.2, Java 25. | [Image Generation and Vision in Spring AI: ImageModel, Prompt Options and Safe Storage](https://ankurm.com/spring-ai-image-generation-imagemodel-s3-minio-safe-storage/) |
| [`batch-llm/`](batch-llm) | 100,000 synthetic support tickets classified by a `ChatClient` inside a Spring Batch 6 chunk step: a Resilience4j rate limiter and retry around the model call (and where Spring Batch's own retry and skip belong instead), replies that are not valid JSON skipped and recorded, a restart after `kill -9` in a second JVM that ends with a byte-identical result, and a cost ledger row committed with every chunk. **The model is a deterministic simulated `ChatModel`, not a language model; token counts are estimates and prices are configurable placeholders.** Spring Boot 4.1.2, Spring Batch 6.0.6, Spring AI 2.0.2, Resilience4j 2.4.0, Java 25. | [Spring AI + Spring Batch: Classifying 100,000 Support Tickets with an LLM](https://ankurm.com/spring-ai-spring-batch-llm-ticket-classification/) |
| [`docker-model-runner/`](docker-model-runner) | The Spring AI OpenAI starter pointed at Docker Model Runner and at Ollama by changing only `base-url` and the model name (no `/v1` is appended: the path is used exactly as given), plus a benchmark runner: time to first token, decode tokens/second, container memory from `docker stats` and tool-call success over 10 runs. Measured with the real `docker/model-runner` v1.2.9 CPU image and Ollama 0.40.13 on one shared 2-vCPU sandbox, Qwen2.5-0.5B Q4_K_M; no GPU, and Docker Desktop's `docker model` path was not used. Spring Boot 4.1.2, Spring AI 2.0.2, Java 25. | [Spring AI with Docker Model Runner vs Ollama: Local LLMs Benchmarked on the Same Laptop](https://ankurm.com/spring-ai-docker-model-runner-vs-ollama/) |
| [`chat-ui-htmx/`](chat-ui-htmx) | A streaming chat page with no JavaScript of our own: htmx and its SSE extension (vendored) post the message and append each token from a `Flux<ServerSentEvent<String>>` over `ChatClient` with `MessageChatMemoryAdvisor` keyed by the HTTP session. Measured traps: EventSource strips one leading space per data line, model text must be HTML-escaped, a cancelled stream left the upstream connection open in 7 of 8 runs until an OkHttp interceptor made it `call.cancel()` (closed in 8 of 8 after), and the SDK's push source ignores back-pressure (`limitRate` changes nothing; a bounded buffer does). Checked in headless Chromium. **The model is a stub OpenAI-compatible SSE server I wrote, not a language model; no real provider was run.** Spring Boot 4.1.2, Spring AI 2.0.2, htmx 2.0.11, Java 25. | [Streaming AI Chat UI with Spring AI, SSE and htmx (No React Needed)](https://ankurm.com/spring-ai-streaming-chat-ui-sse-htmx/) |
Upgrading from Spring AI 1.x: [migration guide](https://ankurm.com/spring-ai-1-to-2-migration-guide/).