Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions client/src/components/settings/LocalLlmLibraryView.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,36 @@ describe('LocalLlmLibraryView information architecture', () => {
});
});

describe('LocalLlmLibraryView model-list diagnostics', () => {
it('shows an Ollama list failure and clears it after a successful empty refresh', async () => {
const emptyOllama = {
installed: true,
available: true,
modelCount: 0,
models: [],
};
getLocalLlmStatus.mockResolvedValueOnce({
backend: 'ollama',
ollama: { ...emptyOllama, modelsError: 'Ollama model list returned no data' },
lmstudio: { installed: false, available: false, modelCount: 0, models: [] },
});

await renderLibrary();

expect(screen.getByText(/Couldn't list Ollama models/)).toHaveTextContent('Ollama model list returned no data');

getLocalLlmStatus.mockResolvedValueOnce({
backend: 'ollama',
ollama: { ...emptyOllama, modelsError: null },
lmstudio: { installed: false, available: false, modelCount: 0, models: [] },
});
const progressHandler = socket.on.mock.calls.find(([event]) => event === 'localLlm:progress')[1];
act(() => progressHandler({ event: 'complete' }));

await waitFor(() => expect(screen.queryByText(/Couldn't list Ollama models/)).toBeNull());
});
});

// The Ollama auto-upgrade banner lives beside the install that triggers it. Its
// only entry point is a model install, which is a Model Library action — before
// the view split the banner rendered inside the Runtimes panel, where the flow
Expand Down
2 changes: 2 additions & 0 deletions server/services/localLlm.js
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,8 @@ export async function getStatus() {
baseUrl: ollamaStatus.baseUrl,
modelCount: ollamaStatus.modelCount,
models: ollamaModels,
contextLength: ollamaStatus.contextLength,
modelsError: ollamaManager.getLastInstalledModelsError(),
// Best installed model for editorial review/editing, surfaced so the
// manuscript editor can suggest it (and warn against the embedding model).
recommendations: { editorial: recommendEditorialModel(ollamaModels, { measured: measuredOllama }) },
Expand Down
40 changes: 40 additions & 0 deletions server/services/localLlm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,46 @@ describe('localLlm', () => {
});
});

describe('getStatus Ollama diagnostics', () => {
it('preserves the manager context-window diagnostics', async () => {
const contextLength = { runtime: 32768, applied: 65536, agentMinimum: 65536 };
mocks.ollama.getStatus.mockResolvedValueOnce({
available: true,
baseUrl: 'x',
version: '0.5.7',
modelCount: 0,
models: [],
contextLength,
});

const status = await svc.getStatus();

expect(status.ollama.contextLength).toEqual(contextLength);
});

it('keeps a null runtime and separates a failed model read from an empty list', async () => {
mocks.ollama.getStatus.mockResolvedValueOnce({
available: true,
baseUrl: 'x',
version: '0.5.7',
modelCount: 0,
models: [],
contextLength: { runtime: null, applied: 65536, agentMinimum: 65536 },
});
mocks.ollama.getLastInstalledModelsError.mockReturnValueOnce('Ollama model list failed');

const failedStatus = await svc.getStatus();

expect(failedStatus.ollama.contextLength.runtime).toBeNull();
expect(failedStatus.ollama.models).toEqual([]);
expect(failedStatus.ollama.modelsError).toBe('Ollama model list failed');

const emptyStatus = await svc.getStatus();
expect(emptyStatus.ollama.models).toEqual([]);
expect(emptyStatus.ollama.modelsError).toBeNull();
});
});

describe('getStatus editorial recommendation with measured evidence', () => {
const installed = [
{ id: 'qwen3.6:35b', name: 'qwen3.6:35b', params: '35B' },
Expand Down