diff --git a/client/src/components/Admin/AddonManager.tsx b/client/src/components/Admin/AddonManager.tsx index 9c0348a13..cba7e72ca 100644 --- a/client/src/components/Admin/AddonManager.tsx +++ b/client/src/components/Admin/AddonManager.tsx @@ -4,7 +4,8 @@ import { useTranslation } from '../../i18n' import { useSettingsStore } from '../../store/settingsStore' import { useAddonStore } from '../../store/addonStore' import { useToast } from '../shared/Toast' -import { Puzzle, ListChecks, Wallet, FileText, CalendarDays, Globe, Briefcase, Image, Terminal, Link2, Compass, BookOpen, MessageCircle, StickyNote, BarChart3, Sparkles, Luggage, Plane } from 'lucide-react' +import { Puzzle, ListChecks, Wallet, FileText, CalendarDays, Globe, Briefcase, Image, Terminal, Link2, Compass, BookOpen, MessageCircle, StickyNote, BarChart3, Sparkles, Luggage, Plane, Server, Cloud } from 'lucide-react' +import CustomSelect from '../shared/CustomSelect' const ICON_MAP = { ListChecks, Wallet, FileText, CalendarDays, Puzzle, Globe, Briefcase, Image, Terminal, Link2, Compass, BookOpen, Plane, @@ -319,9 +320,9 @@ const DEFAULT_OLLAMA_URL = 'http://localhost:11434/v1' /** Curated NuExtract models, pullable via Ollama (HF GGUF for 2.0; library for 1.5). */ const NUEXTRACT_MODELS: { id: string; label: string; note: string; recommended: boolean; vision: boolean }[] = [ - { id: 'hf.co/numind/NuExtract-2.0-2B-GGUF', label: 'NuExtract 2.0 — 2B', note: 'Vision · lightest · commercial license', recommended: true, vision: true }, - { id: 'hf.co/numind/NuExtract-2.0-4B-GGUF', label: 'NuExtract 2.0 — 4B', note: 'Vision · best balance', recommended: true, vision: true }, - { id: 'hf.co/numind/NuExtract-2.0-8B-GGUF', label: 'NuExtract 2.0 — 8B', note: 'Vision · highest quality', recommended: false, vision: true }, + { id: 'hf.co/numind/NuExtract-2.0-2B-GGUF', label: 'NuExtract 2.0 — 2B', note: 'Vision · fastest on CPU · MIT license — recommended', recommended: true, vision: true }, + { id: 'hf.co/numind/NuExtract-2.0-8B-GGUF', label: 'NuExtract 2.0 — 8B', note: 'Vision · highest quality · slower on CPU · MIT license', recommended: false, vision: true }, + { id: 'hf.co/numind/NuExtract-2.0-4B-GGUF', label: 'NuExtract 2.0 — 4B', note: 'Vision · non-commercial (Qwen Research) license', recommended: false, vision: true }, { id: 'nuextract', label: 'NuExtract 1.5 — 3.8B', note: 'Text-only', recommended: false, vision: false }, ] @@ -408,102 +409,128 @@ function LlmParsingConfig({ addon }: { addon: Addon }) { } } - const inputCls = 'w-full rounded-md border border-edge-secondary bg-surface px-2 py-1.5 text-sm text-content' - return ( -
-

- Set instance-wide config (applies to all users). Leave blank to let each user configure their own provider. -

- - - {provider !== 'anthropic' && ( - - )} + const fieldCls = 'w-full rounded-lg border border-edge-secondary bg-surface px-3 py-2 text-sm text-content placeholder:text-content-faint transition-colors focus:border-edge focus:outline-none' + const labelCls = 'mb-1.5 block text-xs font-medium text-content-secondary' + const sectionCls = 'text-[11px] font-semibold uppercase tracking-wide text-content-faint' - {/* Local model management (Ollama) */} - {provider === 'local' && ( -
-
- Installed models - + const providerOptions = [ + { value: 'local', label: 'Local · OpenAI-compatible', icon: , badge: 'Ollama' }, + { value: 'openai', label: 'OpenAI', icon: }, + { value: 'anthropic', label: 'Anthropic', icon: }, + ] + + return ( +
+
+

+ Set instance-wide config (applies to all users). Leave blank to let each user configure their own provider. +

+ + {/* Connection */} +
+
Connection
+
+ Provider + setProvider(String(v))} options={providerOptions} />
- {modelsErr &&

{modelsErr}

} - {!modelsErr && installed.length === 0 && !loadingModels && ( -

No models installed yet — pull one below.

+ {provider !== 'anthropic' && ( + )} - {installed.length > 0 && ( -
- {installed.map(name => ( -
+ + {/* Model */} +
+
Model
+ + + {/* Local model management (Ollama) */} + {provider === 'local' && ( +
+
+ Installed on the server + - ))} +
+ {modelsErr &&

{modelsErr}

} + {!modelsErr && installed.length === 0 && !loadingModels && ( +

No models installed yet — pull one below.

+ )} + {installed.length > 0 && ( +
+ {installed.map(name => ( + + ))} +
+ )} + +
+
Pull a NuExtract model
+
+ {NUEXTRACT_MODELS.map(m => { + const installedHere = isInstalled(m.id) + const isPulling = pulling === m.id + const active = model === m.id + return ( +
+
+
+ {m.label} + {m.recommended && ( + Recommended + )} +
+
{m.note}
+ {isPulling && ( +
+
+
+
+
{pullStatus}{pullPct ? ` · ${pullPct}%` : ''}
+
+ )} +
+ {installedHere ? ( + + ) : ( + + )} +
+ ) + })} +
+
)} +
-
Pull a NuExtract model
-
- {NUEXTRACT_MODELS.map(m => { - const installedHere = isInstalled(m.id) - const isPulling = pulling === m.id - return ( -
-
-
- {m.label} - {m.recommended && ( - Recommended - )} -
-
{m.note}
- {isPulling && ( -
-
-
-
-
{pullStatus} {pullPct ? `· ${pullPct}%` : ''}
-
- )} -
- {installedHere ? ( - - ) : ( - - )} -
- ) - })} -
-
- )} - - - {provider === 'anthropic' && ( -

Anthropic reads PDFs (including scans) natively. Local/OpenAI models receive extracted text — scanned PDFs need Anthropic.

- )} - + +
) }