Skip to content

Commit 357ddac

Browse files
Rename leftover Ollama symbols in provider-agnostic layer (closes #27) (#150)
- AppState: ollamaStatus -> llmProviderStatus - WelcomeView: ollamaReady/Dot/Label/Detail -> llmProviderReady/Dot/Label/Detail; system-check card row label derived from resolvedProvider().displayName() with fallback to configured provider name instead of hardcoding "Ollama" - Footer: ollamaState -> llmProviderState; doc comment updated - SettingsView: state.ollamaStatus -> state.llmProviderStatus - CommandPalette: /settings description generalized to "LLM provider" - LaunchpadRunner: all ollamaStatus field reads/writes updated; buildLlmErrorMessage gains a providerName parameter so timeout/context/ connection error hints name the actual configured provider rather than always saying "Ollama"; new resolvedProviderName(AppState) helper wires all three call sites No behavior change. 668 tests green.
1 parent 9352df7 commit 357ddac

7 files changed

Lines changed: 48 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
9+
### Changed
10+
- **Provider-agnostic TUI symbols**: Renamed `AppState.ollamaStatus` to `llmProviderStatus`; renamed matching helpers in `WelcomeView` (`ollamaReady/Dot/Label/Detail` -> `llmProviderReady/Dot/Label/Detail`), `Footer` (`ollamaState` -> `llmProviderState`), and `SettingsView`. The system-check card now derives the service row label from the resolved provider's `displayName()` instead of hardcoding "Ollama". Error hints in `buildLlmErrorMessage` are driven by the resolved provider name (falling back to "the local model server") rather than hardcoding "Ollama". The `/settings` command palette description now reads "Configure LLM provider and remote standards repo" (closes #27).
11+
912
### Added
1013
- **Gemini CLI compatibility projection**: An opt-in `gemini` `AgentProjection` points Gemini CLI at the canonical `AGENTS.md` instead of emitting a redundant context file. It writes a minimal `.gemini/settings.json` setting `context.fileName` to `AGENTS.md`, so Gemini reads the single source-of-truth contract with no duplicated content to drift out of sync. An existing hand-authored `.gemini/settings.json` is never clobbered. Adds a `CONFIG` file kind for vendor files that point at canonical output. Enable it via `projections: ["claude", "gemini"]` in the standards manifest or by ticking Gemini CLI in the TUI projection picker; no engine change was required (closes #145).
1114
- **Anthropic (Claude) cloud preparation provider**: An opt-in, paid synthesis backend on the `PreparationProvider` SPI (`provider=anthropic`), shipped via the Spring AI Anthropic starter. It is explicit-selection only - `autoDetectable()` is `false`, so auto-detection never routes preparation to a paid API even when a key is present; local AI stays the default. Authentication is vendor-specific (`launchpad.ai.anthropic.api-key`, bound from `LAUNCHPAD_ANTHROPIC_API_KEY`, falling back to the shared key), with its own cloud base URL and `anthropic-version`. A missing key or an unreachable endpoint degrades cleanly to deterministic-only output instead of crashing. Adding it was a single new bean plus its config - no router or health-checker changes (toward #144).

src/main/java/com/acltabontabon/launchpad/tui/AppState.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public enum Phase {
4949
public final TaskFlowState task = new TaskFlowState();
5050

5151
// Local-AI provider readiness (updated from background thread)
52-
public final AtomicReference<LlmProviderStatus> ollamaStatus =
52+
public final AtomicReference<LlmProviderStatus> llmProviderStatus =
5353
new AtomicReference<>(LlmProviderStatus.checking());
5454
public volatile boolean healthCheckRequested = true;
5555

@@ -75,7 +75,7 @@ public enum Phase {
7575
// Set by ProjectSelectView when the support gate rejects the entered project.
7676
public volatile String projectGateError = null;
7777

78-
// Active Ollama model name. Surfaced in the Welcome header.
78+
// Active model name. Surfaced in the Welcome header.
7979
public volatile String activeModel = "";
8080

8181
// Projects screen

src/main/java/com/acltabontabon/launchpad/tui/LaunchpadRunner.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,9 @@ private void renderFrame(Frame frame) {
323323
private void triggerHealthCheckIfNeeded() {
324324
if (!state.healthCheckRequested) return;
325325
state.healthCheckRequested = false;
326-
state.ollamaStatus.set(LlmProviderStatus.checking());
326+
state.llmProviderStatus.set(LlmProviderStatus.checking());
327327

328-
backgroundExecutor.submit(() -> state.ollamaStatus.set(healthChecker.check()));
328+
backgroundExecutor.submit(() -> state.llmProviderStatus.set(healthChecker.check()));
329329
}
330330

331331
private void triggerRemoteStandardsCheckIfNeeded() {
@@ -448,10 +448,10 @@ private void triggerScanIfNeeded() {
448448
// degrades to deterministic output instead of stalling on three
449449
// stream timeouts. Reuse the background result when it is
450450
// terminal; otherwise probe synchronously (bounded ~2s).
451-
var status = state.ollamaStatus.get();
451+
var status = state.llmProviderStatus.get();
452452
if (status == null || status.state() == LlmProviderStatus.State.CHECKING) {
453453
status = healthChecker.check();
454-
state.ollamaStatus.set(status);
454+
state.llmProviderStatus.set(status);
455455
}
456456
// The deterministic provider reports READY but performs no
457457
// synthesis, so it yields deterministic-only output just like an
@@ -502,7 +502,7 @@ private void triggerScanIfNeeded() {
502502
return;
503503
}
504504
state.scan.error = true;
505-
state.scan.message.set(buildLlmErrorMessage("generation", e));
505+
state.scan.message.set(buildLlmErrorMessage("generation", e, resolvedProviderName(state)));
506506
state.scan.complete = true;
507507
}
508508
});
@@ -617,7 +617,7 @@ private void triggerTaskQuestionIfNeeded() {
617617
} catch (Exception e) {
618618
if (state.cancelRequested) return;
619619
state.task.error = true;
620-
state.task.status.set(buildLlmErrorMessage("interview", e));
620+
state.task.status.set(buildLlmErrorMessage("interview", e, resolvedProviderName(state)));
621621
} finally {
622622
state.task.thinking = false;
623623
state.task.opStartedAtMs = 0L;
@@ -725,7 +725,7 @@ private void triggerTaskFinalizeIfNeeded() {
725725
} catch (Exception e) {
726726
if (state.cancelRequested) return;
727727
state.task.error = true;
728-
state.task.status.set(buildLlmErrorMessage("synthesise", e));
728+
state.task.status.set(buildLlmErrorMessage("synthesise", e, resolvedProviderName(state)));
729729
// Clear any partial stream so the user sees the error instead of a half-prompt.
730730
state.task.finalPrompt = "";
731731
} finally {
@@ -737,30 +737,37 @@ private void triggerTaskFinalizeIfNeeded() {
737737
}
738738

739739
/**
740-
* Heuristic error formatter. Local models behind Ollama fail in two distinctive
741-
* ways worth calling out to the user: context-window truncation (the prompt was
740+
* Heuristic error formatter. Local model servers fail in two distinctive ways
741+
* worth calling out to the user: context-window truncation (the prompt was
742742
* bigger than the model's loaded `num_ctx`, often manifests as a 500), and the
743743
* daemon being unreachable. Anything else passes through as-is.
744744
*/
745-
private static String buildLlmErrorMessage(String phase, Throwable e) {
745+
private static String buildLlmErrorMessage(String phase, Throwable e, String providerName) {
746746
var msg = e.getMessage() == null ? e.toString() : e.getMessage();
747747
var lower = msg.toLowerCase();
748748
var base = phase + " failed: " + msg;
749749
if (isTimeout(e)) {
750-
return phase + " timed out: the local model stalled. Check that Ollama is "
751-
+ "responsive, or raise `launchpad.ai.read-timeout` in settings.";
750+
return phase + " timed out: the local model stalled. Check that " + providerName
751+
+ " is responsive, or raise `launchpad.ai.read-timeout` in settings.";
752752
}
753753
if (lower.contains("500") || lower.contains("truncat") || lower.contains("context")) {
754-
return base + " - hint: the prompt may have exceeded Ollama's loaded context window "
755-
+ "(check the Ollama log for 'truncating input prompt'). Try a model with a larger "
754+
return base + " - hint: the prompt may have exceeded the loaded context window "
755+
+ "(check the " + providerName + " log for 'truncating input prompt'). Try a model with a larger "
756756
+ "num_ctx, or simplify the task.";
757757
}
758758
if (lower.contains("connect") || lower.contains("refused") || lower.contains("unreachable")) {
759-
return base + " - hint: Ollama may not be running. Check the Welcome screen's status badge.";
759+
return base + " - hint: " + providerName + " may not be running. Check the Welcome screen's status badge.";
760760
}
761761
return base;
762762
}
763763

764+
private static String resolvedProviderName(AppState state) {
765+
var status = state.llmProviderStatus.get();
766+
if (status == null) return "the local model server";
767+
var provider = status.resolvedProvider();
768+
return provider != null ? provider.displayName() : "the local model server";
769+
}
770+
764771
/** True if any cause in the chain is a {@link java.util.concurrent.TimeoutException}. */
765772
private static boolean isTimeout(Throwable e) {
766773
for (var t = e; t != null; t = t.getCause()) {

src/main/java/com/acltabontabon/launchpad/tui/command/CommandPalette.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public final class CommandPalette {
4141
new Command(
4242
"/settings",
4343
"Settings",
44-
"Configure Ollama and remote standards repo",
44+
"Configure LLM provider and remote standards repo",
4545
(state, runner) -> state.nav.currentScreen = AppState.Screen.SETTINGS
4646
),
4747
new Command(

src/main/java/com/acltabontabon/launchpad/tui/components/Footer.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
/**
2020
* Persistent single-row footer rendered below every view. Hints supplied by
21-
* the active view appear on the left; system status dots (Ollama + Standards)
21+
* the active view appear on the left; system status dots (LLM provider + Standards)
2222
* appear on the right when present.
2323
*/
2424
public final class Footer {
@@ -58,13 +58,13 @@ private static List<Span> quitConfirmSpans() {
5858

5959
private static List<Span> statusSpans(AppState state) {
6060
var out = new ArrayList<Span>();
61-
var ollama = state.ollamaStatus.get();
61+
var llmProvider = state.llmProviderStatus.get();
6262
var standards = state.remoteStandardsStatus.get();
63-
if (ollama != null) {
64-
out.add(StatusDot.dot(ollamaState(ollama)));
65-
var label = ollama.resolvedProvider() == null
63+
if (llmProvider != null) {
64+
out.add(StatusDot.dot(llmProviderState(llmProvider)));
65+
var label = llmProvider.resolvedProvider() == null
6666
? " Local AI "
67-
: " " + ollama.resolvedProvider().displayName() + " ";
67+
: " " + llmProvider.resolvedProvider().displayName() + " ";
6868
out.add(Span.styled(label, Styles.dim()));
6969
}
7070
if (standards != null) {
@@ -74,7 +74,7 @@ private static List<Span> statusSpans(AppState state) {
7474
return out;
7575
}
7676

77-
private static StatusDot.State ollamaState(LlmProviderStatus s) {
77+
private static StatusDot.State llmProviderState(LlmProviderStatus s) {
7878
return switch (s.state()) {
7979
case READY -> StatusDot.State.OK;
8080
case CHECKING -> StatusDot.State.WORKING;

src/main/java/com/acltabontabon/launchpad/tui/view/SettingsView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ private void renderLlmCard(Frame frame, Rect rowArea, AppState state) {
129129
var inner = card.inner(area);
130130
frame.renderWidget(card, area);
131131

132-
var llm = state.ollamaStatus.get();
132+
var llm = state.llmProviderStatus.get();
133133
int innerWidth = inner.width();
134134
var lines = new Line[]{
135135
providerRow(state, innerWidth),

src/main/java/com/acltabontabon/launchpad/tui/view/WelcomeView.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ public void render(Frame frame, Rect area, AppState state) {
8787

8888
// Show the rocket only when both subsystems are non-blocking.
8989
private static boolean allReady(AppState state) {
90-
return ollamaReady(state) && standardsReady(state);
90+
return llmProviderReady(state) && standardsReady(state);
9191
}
9292

93-
private static boolean ollamaReady(AppState state) {
94-
return state.ollamaStatus.get().state() == LlmProviderStatus.State.READY;
93+
private static boolean llmProviderReady(AppState state) {
94+
return state.llmProviderStatus.get().state() == LlmProviderStatus.State.READY;
9595
}
9696

9797
private static boolean standardsReady(AppState state) {
@@ -104,13 +104,17 @@ private static boolean standardsReady(AppState state) {
104104

105105
private void renderSystemCheck(Frame frame, Rect area, AppState state) {
106106
var snap = settings.snapshot();
107-
var ollama = state.ollamaStatus.get();
107+
var llmProvider = state.llmProviderStatus.get();
108108
var standards = state.remoteStandardsStatus.get();
109109

110+
var providerName = llmProvider.resolvedProvider() != null
111+
? llmProvider.resolvedProvider().displayName()
112+
: snap.provider() != null ? snap.provider().displayName() : "Local AI";
113+
110114
int cardWidth = Math.min(area.width() - 4, 80);
111115
var rows = new java.util.ArrayList<Line>();
112116
rows.add(blank());
113-
rows.add(serviceRow("Ollama", ollamaDot(ollama), ollamaLabel(ollama), ollamaDetail(ollama, snap), cardWidth));
117+
rows.add(serviceRow(providerName, llmProviderDot(llmProvider), llmProviderLabel(llmProvider), llmProviderDetail(llmProvider, snap), cardWidth));
114118
rows.add(blank());
115119
rows.add(serviceRow("Standards", standardsDot(standards), standardsLabelText(standards), standardsDetail(standards), cardWidth));
116120

@@ -155,15 +159,15 @@ private static Line serviceRow(String name, StatusDot.State dot, String label, S
155159
);
156160
}
157161

158-
private static StatusDot.State ollamaDot(LlmProviderStatus s) {
162+
private static StatusDot.State llmProviderDot(LlmProviderStatus s) {
159163
return switch (s.state()) {
160164
case READY -> StatusDot.State.OK;
161165
case CHECKING -> StatusDot.State.WORKING;
162166
case DAEMON_DOWN, MODEL_MISSING -> StatusDot.State.ERROR;
163167
};
164168
}
165169

166-
private String ollamaLabel(LlmProviderStatus s) {
170+
private String llmProviderLabel(LlmProviderStatus s) {
167171
return switch (s.state()) {
168172
case READY -> "ready";
169173
case CHECKING -> "checking " + Spinner.frame(tick / 4);
@@ -172,7 +176,7 @@ private String ollamaLabel(LlmProviderStatus s) {
172176
};
173177
}
174178

175-
private static String ollamaDetail(LlmProviderStatus s, LaunchpadSettings.Snapshot snap) {
179+
private static String llmProviderDetail(LlmProviderStatus s, LaunchpadSettings.Snapshot snap) {
176180
var primary = snap.baseUrl() + " " + Icons.SEP + " " + snap.model();
177181
if (s.hint() != null && !s.hint().isBlank()) {
178182
return primary + " " + Icons.SEP + " " + s.hint();

0 commit comments

Comments
 (0)