diff --git a/admin/app/services/ollama_service.ts b/admin/app/services/ollama_service.ts
index be319e9..174ffd5 100644
--- a/admin/app/services/ollama_service.ts
+++ b/admin/app/services/ollama_service.ts
@@ -764,7 +764,7 @@ export class OllamaService {
): Promise<{ models: NomadOllamaModel[]; hasMore: boolean } | null> {
try {
const models = await this.retrieveAndRefreshModels(sort, force)
- if (!models) {
+ if (!models || models.length === 0) {
logger.warn(
'[OllamaService] Returning fallback recommended models due to failure in fetching available models'
)
@@ -819,7 +819,10 @@ export class OllamaService {
try {
if (!force) {
const cachedModels = await this.readModelsFromCache()
- if (cachedModels) {
+ // An empty cached array (e.g. written from a transient empty upstream
+ // response) must not be treated as valid data — fall through to a
+ // fresh fetch and, failing that, the fallback list.
+ if (cachedModels && cachedModels.length > 0) {
logger.info('[OllamaService] Using cached available models data')
return this.sortModels(cachedModels, sort)
}
@@ -832,7 +835,7 @@ export class OllamaService {
const baseUrl = env.get('NOMAD_API_URL') || NOMAD_API_DEFAULT_BASE_URL
const fullUrl = new URL(NOMAD_MODELS_API_PATH, baseUrl).toString()
- const response = await axios.get(fullUrl)
+ const response = await axios.get(fullUrl, { timeout: 10000 })
if (!response.data || !Array.isArray(response.data.models)) {
logger.warn(
`[OllamaService] Invalid response format when fetching available models: ${JSON.stringify(response.data)}`
@@ -849,6 +852,16 @@ export class OllamaService {
}))
.filter((model) => model.tags.length > 0)
+ // A successful-but-empty upstream response (0 models, or all filtered out
+ // as cloud-only) is a soft failure: return null so the caller serves the
+ // fallback list, and don't poison the 24h cache with an empty array.
+ if (noCloud.length === 0) {
+ logger.warn(
+ '[OllamaService] Nomad API returned no usable (non-cloud) models; using fallback'
+ )
+ return null
+ }
+
await this.writeModelsToCache(noCloud)
return this.sortModels(noCloud, sort)
} catch (error) {
diff --git a/admin/inertia/pages/easy-setup/index.tsx b/admin/inertia/pages/easy-setup/index.tsx
index da97cf8..89515e8 100644
--- a/admin/inertia/pages/easy-setup/index.tsx
+++ b/admin/inertia/pages/easy-setup/index.tsx
@@ -11,7 +11,7 @@ import TierSelectionModal from '~/components/TierSelectionModal'
import WikipediaSelector from '~/components/WikipediaSelector'
import LoadingSpinner from '~/components/LoadingSpinner'
import Alert from '~/components/Alert'
-import { IconCheck, IconChevronDown, IconChevronUp, IconCpu, IconBooks } from '@tabler/icons-react'
+import { IconCheck, IconCpu, IconBooks } from '@tabler/icons-react'
import StorageProjectionBar from '~/components/StorageProjectionBar'
import { useNotifications } from '~/context/NotificationContext'
import useInternetStatus from '~/hooks/useInternetStatus'
@@ -81,30 +81,10 @@ function buildCoreCapabilities(aiAssistantName: string): Capability[] {
]
}
-const ADDITIONAL_TOOLS: Capability[] = [
- {
- id: 'notes',
- name: 'Notes',
- technicalName: 'FlatNotes',
- description: 'Simple note-taking app with local storage',
- features: ['Markdown support', 'All notes stored locally', 'No account required'],
- services: [SERVICE_NAMES.FLATNOTES],
- icon: 'IconNotes',
- },
- {
- id: 'datatools',
- name: 'Data Tools',
- technicalName: 'CyberChef',
- description: 'Swiss Army knife for data encoding, encryption, and analysis',
- features: [
- 'Encode/decode data (Base64, hex, etc.)',
- 'Encryption and hashing tools',
- 'Data format conversion',
- ],
- services: [SERVICE_NAMES.CYBERCHEF],
- icon: 'IconChefHat',
- },
-]
+// Additional tools (Notes, Data Tools, and the rest of the catalog) are no
+// longer surfaced in onboarding — they live in Supply Depot, where the full
+// app catalog is browsable any time. Step 1 keeps the focus on the three core
+// capabilities and points users to Supply Depot for everything else.
type WizardStep = 1 | 2 | 3 | 4 | 5
@@ -123,14 +103,14 @@ export default function EasySetupWizard(props: {
const [selectedMapCollections, setSelectedMapCollections] = useState
+ Only need a specific country, or want the whole world? Individual countries and a full + global map can be installed any time from the{' '} + + . +
+{(() => { - const count = [...CORE_CAPABILITIES, ...ADDITIONAL_TOOLS].filter((cap) => + const count = CORE_CAPABILITIES.filter((cap) => cap.services.some((s) => selectedServices.includes(s)) ).length return `${count} ${count === 1 ? 'capability' : 'capabilities'}`