atiaomar1978-hub commented on code in PR #26144:
URL: https://github.com/apache/camel/pull/26144#discussion_r3944424283
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/LlmClient.java:
##########
@@ -1795,7 +1795,13 @@ private boolean tryOpenAi() {
apiKey = key;
openAiAuthMode = OpenAiAuthMode.bearer;
if (url == null || url.isBlank()) {
- url = "https://api.openai.com";
+ // LLM_BASE_URL / OPENAI_BASE_URL let users point at any
OpenAI-compatible
Review Comment:
_AI-generated review on behalf of atiaomar1978-hub_
**Tests needed:** User-facing behavior change (`LLM_API_KEY` +
`LLM_BASE_URL` / `OPENAI_BASE_URL`). Please add a focused unit test (similar to
`LlmClientAzureTest`) asserting that `tryOpenAi()` picks up the env base URL
and falls back to `https://api.openai.com` when unset. Security-wise this is
operator-controlled config — no concern.
##########
parent/pom.xml:
##########
@@ -4545,6 +4545,40 @@
</plugins>
</build>
</profile>
+ <profile>
+ <!-- Propagate ci.env.name to forked surefire/failsafe JVMs so that
Review Comment:
_AI-generated review on behalf of atiaomar1978-hub_
**Scope / CI:** Valuable fix for `@DisabledIfSystemProperty(named =
"ci.env.name")`, but unrelated to Ollama doctor UX. CI is still **red** on Java
17/25 with failsafe failures in `camel-spring-ai-image` and `camel-docling`
during `regen.sh`. Please verify this profile activates when MVND_OPTS sets
`-Dci.env.name=github.com`. Consider a separate PR for CI plumbing.
##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopup.java:
##########
@@ -277,6 +286,117 @@ private static boolean isPortInUse(int port) {
}
}
+ private void checkOllama(List<Line> result) {
+ try {
+ HttpClient client = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(3))
+ .build();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:11434/api/tags"))
+ .timeout(Duration.ofSeconds(3))
+ .GET()
+ .build();
+ HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() == 200) {
+ List<String> models = parseOllamaModels(response.body());
+ if (models.isEmpty()) {
+ result.add(Line.from(
+ Span.raw(TuiIcons.indent(TuiIcons.MCP)),
+ Span.styled(String.format("%-14s", "Ollama"),
Theme.muted()),
+ Span.raw(String.format("%-30s", "Running — no
models pulled yet")),
+ Span.raw(" " + TuiIcons.WARN)));
+ result.add(Line.from(Span.styled(" Run:
ollama pull qwen2.5:14b",
+ Style.EMPTY.dim())));
+ } else {
+ boolean allSmall =
models.stream().allMatch(DoctorPopup::isSmallModel);
+ String icon = allSmall ? TuiIcons.WARN : TuiIcons.OK;
+ result.add(Line.from(
+ Span.raw(TuiIcons.indent(TuiIcons.MCP)),
+ Span.styled(String.format("%-14s", "Ollama"),
Theme.muted()),
+ Span.raw(String.format("%-30s", models.size() + "
model(s) available")),
+ Span.raw(" " + icon)));
+ result.add(Line.from(Span.styled(
+ " " +
TuiHelper.truncate(String.join(", ", models), 44),
+ Style.EMPTY.dim())));
+ if (allSmall) {
+ result.add(Line.from(Span.styled(
+ " F8 needs ≥14B — run:
ollama pull qwen2.5:14b",
+ Style.EMPTY.dim())));
+ }
+ }
+ } else {
+ result.add(Line.from(
+ Span.raw(TuiIcons.indent(TuiIcons.MCP)),
+ Span.styled(String.format("%-14s", "Ollama"),
Theme.muted()),
+ Span.raw(String.format("%-30s", "Not running
(optional)")),
+ Span.raw(" " + TuiIcons.WARN)));
+ result.add(Line.from(Span.styled(" Run:
ollama serve",
+ Style.EMPTY.dim())));
+ }
+ } catch (Exception e) {
+ result.add(Line.from(
+ Span.raw(TuiIcons.indent(TuiIcons.MCP)),
+ Span.styled(String.format("%-14s", "Ollama"),
Theme.muted()),
+ Span.raw(String.format("%-30s", "Not running (optional)")),
+ Span.raw(" " + TuiIcons.WARN)));
+ result.add(Line.from(Span.styled(" Run: ollama
serve",
+ Style.EMPTY.dim())));
+ }
+ }
+
+ private boolean isOllamaRunning() {
+ try {
+ HttpClient client = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(2))
+ .build();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:11434/api/tags"))
+ .timeout(Duration.ofSeconds(2))
+ .GET()
+ .build();
+ return client.send(request,
HttpResponse.BodyHandlers.discarding()).statusCode() == 200;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private static boolean isSmallModel(String name) {
Review Comment:
_AI-generated review on behalf of atiaomar1978-hub_
**Heuristic edge cases:** `isSmallModel()` only recognizes tags matching
`\d+b` after the last `:`. Models without a size suffix (e.g. some `granite4`
tags) return `false` (treated as OK), while a mix of small + one unknown tag
yields `allSmall=false` and hides the WARN. Worth a short unit test matrix
and/or a comment that this is best-effort.
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Doctor.java:
##########
@@ -133,6 +143,54 @@ private void checkContainerRuntime() {
printer().printf(" Container: Not found (optional — needed for
running external infra services)%n");
}
+ private void checkOllama() {
Review Comment:
_AI-generated review on behalf of atiaomar1978-hub_
**Reuse existing detection:** `LlmClient` already probes Ollama via
`tryInfraOllama()` (camel infra PID files) and `tryDefaultOllama()`
(`localhost:11434`), and exposes `listModels()` for `/api/tags`. This inline
`HttpClient` + `parseOllamaModels()` duplicates that logic and **misses**
infra-managed Ollama on non-default ports. A shared helper delegating to
`LlmClient.detectEndpoint()` + `listModels()` would keep doctor/TUI/ask
consistent.
##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DoctorPopup.java:
##########
@@ -298,6 +418,8 @@ private void checkAiProvider(List<Line> result) {
provider = "watsonx.ai";
} else if (envSet("LLM_API_KEY")) {
provider = "Custom (LLM_API_KEY)";
+ } else if (isOllamaRunning()) {
Review Comment:
_AI-generated review on behalf of atiaomar1978-hub_
**Performance / duplication:** Opening the doctor popup triggers **two**
HTTP probes to `localhost:11434/api/tags` — once in `checkOllama()` and again
here via `isOllamaRunning()`. Consider passing the result from `checkOllama`
into `checkAiProvider`, or reusing a single shared
`OllamaDoctorSupport.detect()` call for both rows.
##########
components/camel-spring-parent/camel-spring-ai/camel-spring-ai-image/src/test/java/org/apache/camel/component/springai/image/SpringAiImageOllamaIT.java:
##########
@@ -67,7 +68,18 @@ public class SpringAiImageOllamaIT extends CamelTestSupport {
Path tempDir;
@RegisterExtension
Review Comment:
_AI-generated review on behalf of atiaomar1978-hub_
**Scope:** Pulling `x/flux2-klein:4b` via custom
`OllamaServiceConfiguration` is a sensible IT fix, but unrelated to
CAMEL-24631. Also: `@DisabledIfSystemProperty(..., disabledReason = "Disabled
unless running in CI")` is misleading — the test is disabled **when**
`ci.env.name` **is** set (i.e. in CI).
##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java:
##########
@@ -1556,6 +1560,78 @@ private void renderScrollbar(Frame frame, Rect area, int
totalLines, int visible
frame.renderWidget(Paragraph.from(new dev.tamboui.text.Text(lines,
dev.tamboui.layout.Alignment.LEFT)), area);
}
+ private String buildAiSetupGuide() {
Review Comment:
_AI-generated review on behalf of atiaomar1978-hub_
**Tests:** Substantial UX addition. Consider a unit test that when
`initError` starts with `"No LLM service reachable"`, the rendered markdown
includes key sections (Ollama install, ≥14B warning, `LLM_BASE_URL`).
`AiPanelTest` already has patterns for injecting a test client.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]