mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(admin): recommend the local model that also reads images
The one model the addon offers to pull was Qwen3 8B, which is text-only, so a scanned page or a photographed document reached it as no text at all and came back empty. Qwen3.5 4B is multimodal at a third of the size: one download covers every kind of document instead of leaving the operator to work out that the recommended model cannot read half of them. The list stays one entry long. A self-hosted host needs a model, not a menu, and any other id can still be typed in or picked from what Ollama already has.
This commit is contained in:
@@ -473,7 +473,7 @@ describe('AddonManager', () => {
|
||||
it('FE-ADMIN-ADDON-021: the local provider lists installed models and a chip fills the model field', async () => {
|
||||
const user = userEvent.setup();
|
||||
const urls: (string | null)[] = [];
|
||||
server.use(addonsRoute([llmAddon({ provider: 'local' })]), modelsRoute(['qwen3:8b', 'llama3:8b'], urls));
|
||||
server.use(addonsRoute([llmAddon({ provider: 'local' })]), modelsRoute(['qwen3.5:4b', 'llama3:8b'], urls));
|
||||
render(<AddonManager />);
|
||||
|
||||
await screen.findByText('Installed on the server');
|
||||
@@ -483,9 +483,9 @@ describe('AddonManager', () => {
|
||||
await user.click(screen.getByRole('button', { name: 'llama3:8b' }));
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('llama3:8b');
|
||||
|
||||
// qwen3:8b is already installed, so the recommended row offers "Use" instead of "Pull"
|
||||
// qwen3.5:4b is already installed, so the recommended row offers "Use" instead of "Pull"
|
||||
await user.click(screen.getByRole('button', { name: 'Use' }));
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('qwen3:8b');
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('qwen3.5:4b');
|
||||
expect(screen.getByRole('button', { name: 'Selected' })).toBeDisabled();
|
||||
});
|
||||
|
||||
@@ -545,7 +545,7 @@ describe('AddonManager', () => {
|
||||
addonsRoute([llmAddon({ provider: 'local' })]),
|
||||
http.get('/api/admin/llm/local/models', () => {
|
||||
modelCalls += 1;
|
||||
return HttpResponse.json({ models: modelCalls === 1 ? [] : [{ name: 'qwen3:8b', size: 1 }] });
|
||||
return HttpResponse.json({ models: modelCalls === 1 ? [] : [{ name: 'qwen3.5:4b', size: 1 }] });
|
||||
}),
|
||||
http.post('/api/admin/llm/local/pull', async ({ request }) => {
|
||||
pulled = await request.json();
|
||||
@@ -565,8 +565,8 @@ describe('AddonManager', () => {
|
||||
expect(screen.getByText('starting…')).toBeInTheDocument();
|
||||
|
||||
await screen.findByText('Model pulled');
|
||||
expect(pulled).toEqual({ baseUrl: 'http://localhost:11434/v1', model: 'qwen3:8b' });
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('qwen3:8b');
|
||||
expect(pulled).toEqual({ baseUrl: 'http://localhost:11434/v1', model: 'qwen3.5:4b' });
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('qwen3.5:4b');
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: 'Selected' })).toBeDisabled());
|
||||
});
|
||||
|
||||
|
||||
@@ -306,9 +306,10 @@ const DEFAULT_OLLAMA_URL = 'http://localhost:11434/v1'
|
||||
|
||||
/** Curated models the local extractor is tuned for, pullable via Ollama. The router drives
|
||||
* one model per document via Ollama's grammar-constrained `format`; "thinking" is disabled
|
||||
* automatically, so the Qwen3 family works without any tuning. A host only needs one. */
|
||||
* automatically, so the Qwen3 family works without any tuning. A host only needs one — and
|
||||
* Qwen3.5 reads images as well as text, so that one covers every kind of document. */
|
||||
const RECOMMENDED_MODELS: { id: string; label: string; note: string; recommended: boolean; vision: boolean }[] = [
|
||||
{ id: 'qwen3:8b', label: 'Qwen3 — 8B', note: 'Recommended · best extraction quality & speed on CPU (thinking auto-disabled) · Apache-2.0', recommended: true, vision: false },
|
||||
{ id: 'qwen3.5:4b', label: 'Qwen3.5 — 4B', note: 'Recommended · reads images as well as text, so one model covers every document (3.4 GB, 256K context, thinking auto-disabled)', recommended: true, vision: true },
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -351,9 +351,10 @@ const DEFAULT_OLLAMA_URL = 'http://localhost:11434/v1'
|
||||
|
||||
/** Curated models the local extractor is tuned for, pullable via Ollama. The router drives
|
||||
* one model per document via Ollama's grammar-constrained `format`; "thinking" is disabled
|
||||
* automatically, so the Qwen3 family works without any tuning. A host only needs one. */
|
||||
* automatically, so the Qwen3 family works without any tuning. A host only needs one — and
|
||||
* Qwen3.5 reads images as well as text, so that one covers every kind of document. */
|
||||
const RECOMMENDED_MODELS: { id: string; label: string; note: string; recommended: boolean; vision: boolean }[] = [
|
||||
{ id: 'qwen3:8b', label: 'Qwen3 — 8B', note: 'Recommended · best extraction quality & speed on CPU (thinking auto-disabled) · Apache-2.0', recommended: true, vision: false },
|
||||
{ id: 'qwen3.5:4b', label: 'Qwen3.5 — 4B', note: 'Recommended · reads images as well as text, so one model covers every document (3.4 GB, 256K context, thinking auto-disabled)', recommended: true, vision: true },
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -368,7 +368,7 @@ describe('MAdminAddonManager', () => {
|
||||
it('FE-MOB-AADD-017: the local provider lists installed models and selecting a chip fills the model field', async () => {
|
||||
const user = userEvent.setup();
|
||||
const urls: (string | null)[] = [];
|
||||
server.use(addonsRoute([llmAddon({ provider: 'local' })]), modelsRoute(['qwen3:8b', 'llama3:8b'], urls));
|
||||
server.use(addonsRoute([llmAddon({ provider: 'local' })]), modelsRoute(['qwen3.5:4b', 'llama3:8b'], urls));
|
||||
render(<MAdminAddonManager />);
|
||||
|
||||
await screen.findByText('Installed on the server');
|
||||
@@ -378,9 +378,9 @@ describe('MAdminAddonManager', () => {
|
||||
await user.click(screen.getByRole('button', { name: 'llama3:8b' }));
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('llama3:8b');
|
||||
|
||||
// qwen3:8b is installed, so the recommended row offers "Use" instead of "Pull".
|
||||
// qwen3.5:4b is installed, so the recommended row offers "Use" instead of "Pull".
|
||||
await user.click(screen.getByRole('button', { name: 'Use' }));
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('qwen3:8b');
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('qwen3.5:4b');
|
||||
expect(screen.getByRole('button', { name: 'Selected' })).toBeDisabled();
|
||||
});
|
||||
|
||||
@@ -449,7 +449,7 @@ describe('MAdminAddonManager', () => {
|
||||
addonsRoute([llmAddon({ provider: 'local' })]),
|
||||
http.get('/api/admin/llm/local/models', () => {
|
||||
modelCalls += 1;
|
||||
return HttpResponse.json({ models: modelCalls === 1 ? [] : [{ name: 'qwen3:8b', size: 1 }] });
|
||||
return HttpResponse.json({ models: modelCalls === 1 ? [] : [{ name: 'qwen3.5:4b', size: 1 }] });
|
||||
}),
|
||||
http.post('/api/admin/llm/local/pull', async ({ request }) => {
|
||||
pulled = await request.json();
|
||||
@@ -470,8 +470,8 @@ describe('MAdminAddonManager', () => {
|
||||
expect(screen.getByText('starting…')).toBeInTheDocument();
|
||||
|
||||
await screen.findByText('Model pulled');
|
||||
expect(pulled).toEqual({ baseUrl: 'http://localhost:11434/v1', model: 'qwen3:8b' });
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('qwen3:8b');
|
||||
expect(pulled).toEqual({ baseUrl: 'http://localhost:11434/v1', model: 'qwen3.5:4b' });
|
||||
expect(screen.getByPlaceholderText('select or pull below')).toHaveValue('qwen3.5:4b');
|
||||
// Reloaded models now contain the pulled one, so the row switches to "Selected".
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: 'Selected' })).toBeDisabled());
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ When you enable the addon, a configuration panel appears directly under it in [A
|
||||
- **Provider** — Local · OpenAI-compatible, OpenAI, or Anthropic.
|
||||
- **Base URL** — shown for every provider except Anthropic. Defaults to `http://localhost:11434/v1` for a local Ollama server, or `https://api.openai.com/v1` for OpenAI. Point it at any OpenAI-compatible endpoint here.
|
||||
- **API key** — optional for a local server (`(often not required)`), required for the cloud providers. Stored **encrypted**; it is shown masked (`••••••••`) once saved, and leaving it unchanged keeps the stored key.
|
||||
- **Model** — the model id (e.g. `qwen3:8b`, `gpt-4o`, `claude-opus-4-8`).
|
||||
- **Model** — the model id (e.g. `qwen3.5:4b`, `gpt-4o`, `claude-opus-4-8`).
|
||||
|
||||
If you set a provider and model here, it applies to **all users** and overrides their personal settings. Leave the panel blank to let each user bring their own model (see below).
|
||||
|
||||
@@ -46,7 +46,7 @@ If you set a provider and model here, it applies to **all users** and overrides
|
||||
With the **Local** provider selected, the panel manages your Ollama server directly:
|
||||
|
||||
- **Installed on the server** lists the models Ollama already has, with a **Refresh** button. Click a model to select it.
|
||||
- **Pull a recommended model** downloads a model with a live progress bar. The one recommended model is **Qwen3 — 8B** (`qwen3:8b`) — *best extraction quality & speed on CPU (thinking auto-disabled) · Apache-2.0*. Once the pull finishes it is selected automatically.
|
||||
- **Pull a recommended model** downloads a model with a live progress bar. The one recommended model is **Qwen3.5 — 4B** (`qwen3.5:4b`, 3.4 GB, 256K context) — it reads images as well as text, so a single download covers every kind of document, and "thinking" is disabled automatically. Once the pull finishes it is selected automatically.
|
||||
|
||||
You can also select any other model already installed on the server, or type a model id by hand.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user