The previous `callLocal` re-threw the raw browser `NetworkError`
("NetworkError when attempting to fetch resource" / "Failed to
fetch"), which is what happens when the browser blocks a cross-origin
request because the local server has not enabled CORS. Users running
LMStudio or older Ollama versions had no way to know that — the error
just said "network error" with no actionable hint.
- `callLocal` now catches the fetch `TypeError` and throws a
descriptive `Error` that names the endpoint and includes the exact
CORS fix for the detected server (LMStudio → "Local Server → ⚙ →
enable CORS", Ollama → set `OLLAMA_ORIGINS="*"`). The original
error is preserved via `Error.cause`.
- New `testLocalConnection` helper that hits `${endpoint}/models`
and returns either `{ ok, models }` or `{ ok, error }` with the
same CORS-aware error message. The Authorization header is sent when
an API key is configured.
- New `detectLocalServer` and `corsHelpText` helpers (port-based
detection for LMStudio 1234 / Ollama 11434).
- InputScreen now renders a "Test" button next to the Local Endpoint
field. Success shows the model list ("2 models available: llama3.2,
qwen2.5-coder:7b"); failure shows the CORS-aware error inline. The
label is now just "Local Endpoint" with the per-server URLs moved
to a hint line that explicitly mentions the required `/v1` path
(LMStudio users were previously typing `http://127.0.0.1:1234`
without `/v1`, which would 404 once CORS was fixed).
- Bump `tsconfig.json` `lib` from ES2020 to ES2022 so `Error.cause`
typechecks (added in ES2022; `target` stays at ES2020 for
compatibility).
- README: new "Local LLM provider (Ollama / LMStudio)" section with
startup commands, CORS instructions, and a troubleshooting line
pointing users to the in-app error message.
- 11 new tests for the local client (network error wrapping, test
connection success/failure, non-JSON response, server detection,
CORS help text). 4 new InputScreen tests for the provider UI and
Test button. 186/186 pass.
The root App container and the ScreenLayout wrapper both used
`overflow: hidden`, which clipped the InputScreen when the API
configuration panel was expanded. With the new Local (Ollama / LMStudio)
fields added in the previous commit, the panel grew tall enough that
the Save button fell below the viewport with no way to reach it.
- Switch ScreenLayout from `overflow: hidden` to `overflow: auto` so
any screen can scroll when its content exceeds the viewport.
- Switch the centered layout alignment to `safe center` so a tall
centered screen stays scrolled to the top (the previous `center`
would push the top of the content off-screen once it overflowed).
- Update the ScreenLayout test to match the new alignment values.
Adds the ability to run AI analysis against a local model served by
Ollama or LMStudio (or any OpenAI-compatible local server), in addition
to the existing OpenRouter cloud provider.
How it works:
- New 'Local (Ollama / LMStudio)' option in the API configuration panel
alongside the existing OpenRouter key field.
- User can override the local endpoint (default http://localhost:11434/v1
— Ollama's default; LMStudio users set http://localhost:1234/v1),
the model name (default 'llama3.2'), and an optional API key (LMStudio
defaults to 'lm-studio' when auth is enabled).
- The selected provider, endpoint, model and local API key are persisted
in localStorage alongside the existing GitHub / OpenRouter key
remember-me flow.
- Concurrent requests are throttled to 2 for the local provider (vs 15
for OpenRouter) to avoid overloading a local GPU.
Architecture:
- New 'src/lib/api/local.ts' client that hits an OpenAI-compatible
/v1/chat/completions endpoint. Drops 'response_format: json_object'
because not every local backend supports it; the prompt and the
existing parseAnalysisResponse fallback already handle JSON
extraction and partial recovery.
- New 'src/lib/api/provider.ts' module that exposes the
analyzeIssue / analyzeAllIssues surface and dispatches to either
openrouter or local based on the store's aiProvider value.
- openrouter.ts is now a thin client with explicit (apiKey, model,
baseUrl) parameters — no implicit store reads.
- useIssueAnalysis now imports from ./api/provider and logs the active
provider + endpoint/model instead of just the model name.
Other:
- New VITE_LOCAL_ENDPOINT / VITE_LOCAL_MODEL env vars (documented in
.env.example) used to override the build-time defaults.
- New AIProvider / LocalProviderConfig types in src/lib/types.ts.
- 19 new unit tests covering the local client and the provider
dispatch (170/170 pass).
Node 22+ exposes an experimental `localStorage` global getter that
returns `undefined` unless `--localstorage-file` is provided. Vitest's
`populateGlobal` skips keys that are already in `global` (unless they
are in its internal KEYS allowlist), so it never copies jsdom's working
`localStorage` over Node's broken one. As a result, every test that
touched `localStorage` (Header.test.tsx, InputScreen.test.tsx, and
any test calling `localStorage.clear()`) crashed with
`Cannot read properties of undefined (reading 'getItem')`.
The test setup file now creates a dedicated JSDOM instance and assigns
its `localStorage` and `sessionStorage` to `globalThis` before any
test runs, so the storage APIs work in the jsdom test environment
regardless of the host Node version.
Replace the previously hardcoded default arcee-ai/trinity-large-preview:free
model with openai/gpt-oss-20b:free in both the runtime default and the
analysis log message.
Also document the existing VITE_MODEL_NAME override in .env.example so
users can easily override the default model from their environment.
- Replaced local parseRepoInput in useIssueAnalysis.ts with import from validators.ts
- Updated formatTimeAgo in formatters.ts to accept number | string overload
- Replaced local formatTimeAgo in InputScreen.tsx with import from formatters.ts