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]

Reply via email to