HomePilot Deploy Bot commited on
Commit
9124672
·
unverified ·
1 Parent(s): 7118166

feat(hf): OllaBridge provider class across all Model Type tabs

Browse files
.gitattributes CHANGED
@@ -1,9 +1,9 @@
1
- *.jpg filter=lfs diff=lfs merge=lfs -text
2
  *.jpeg filter=lfs diff=lfs merge=lfs -text
 
3
  *.svg filter=lfs diff=lfs merge=lfs -text
4
  *.ico filter=lfs diff=lfs merge=lfs -text
 
 
 
5
  *.png filter=lfs diff=lfs merge=lfs -text
6
- *.webp filter=lfs diff=lfs merge=lfs -text
7
  *.woff filter=lfs diff=lfs merge=lfs -text
8
- *.woff2 filter=lfs diff=lfs merge=lfs -text
9
- *.hpersona filter=lfs diff=lfs merge=lfs -text
 
 
1
  *.jpeg filter=lfs diff=lfs merge=lfs -text
2
+ *.webp filter=lfs diff=lfs merge=lfs -text
3
  *.svg filter=lfs diff=lfs merge=lfs -text
4
  *.ico filter=lfs diff=lfs merge=lfs -text
5
+ *.hpersona filter=lfs diff=lfs merge=lfs -text
6
+ *.jpg filter=lfs diff=lfs merge=lfs -text
7
+ *.woff2 filter=lfs diff=lfs merge=lfs -text
8
  *.png filter=lfs diff=lfs merge=lfs -text
 
9
  *.woff filter=lfs diff=lfs merge=lfs -text
 
 
frontend/.cache-bust CHANGED
@@ -1 +1 @@
1
- 1783228502
 
1
+ 1783229390
frontend/src/ui/Models.tsx CHANGED
@@ -588,6 +588,10 @@ export default function ModelsView(props: ModelsParams) {
588
 
589
  const [modelType, setModelType] = useState<'chat' | 'multimodal' | 'image' | 'video' | 'edit' | 'enhance' | 'addons' | 'lora'>('chat')
590
  const [provider, setProvider] = useState<string>(props.providerChat || 'ollama')
 
 
 
 
591
 
592
  const defaultBaseUrl = useMemo(() => {
593
  if (modelType === 'chat') return props.baseUrlChat || ''
@@ -660,14 +664,18 @@ export default function ModelsView(props: ModelsParams) {
660
 
661
  // Filter providers based on model type
662
  const availableProviders = useMemo(() => {
 
 
 
 
663
  if (modelType === 'chat') {
664
  // For chat: all providers EXCEPT comfyui and civitai
665
- return providers.filter(p => p.name !== 'comfyui' && p.name !== 'civitai')
666
  } else if (modelType === 'multimodal') {
667
- // Multimodal: only Ollama (vision models run locally)
668
- return providers.filter(p => p.name === 'ollama')
669
  } else {
670
- // For image/video/edit/enhance: comfyui + civitai (if experimental enabled)
671
  const filtered = providers.filter(p => p.name === 'comfyui')
672
 
673
  // Add Civitai only for image/video (Civitai API only supports these types)
@@ -679,11 +687,14 @@ export default function ModelsView(props: ModelsParams) {
679
  })
680
  }
681
 
682
- return filtered
683
  }
684
  }, [providers, modelType, props.experimentalCivitai])
685
 
686
  useEffect(() => {
 
 
 
687
  // When modelType changes, update provider + baseUrl defaults
688
  if (modelType === 'chat') {
689
  // Switch to a chat provider (prefer the one from settings, or default to ollama)
@@ -762,7 +773,9 @@ export default function ModelsView(props: ModelsParams) {
762
  // Skip fetching installed models for remote API providers and Civitai
763
  // Remote providers: they're cloud services, not local installations
764
  // Civitai: download-only provider, models get installed to ComfyUI after download
765
- const skipProviders = ['openai', 'claude', 'watsonx', 'civitai']
 
 
766
 
767
  if (skipProviders.includes(provider)) {
768
  setInstalled([])
@@ -2060,9 +2073,14 @@ export default function ModelsView(props: ModelsParams) {
2060
  {/* Body */}
2061
  <div className="flex-1 overflow-y-auto px-4 sm:px-8 py-6 scrollbar-hide">
2062
  <div className="flex flex-col gap-4 max-w-7xl mx-auto">
2063
- {/* OllaBridge provider class — models synced from the user's linked
2064
- HomePilot / GPU nodes, usable from web & mobile via the relay. */}
2065
- <OllaBridgeModels />
 
 
 
 
 
2066
  {/* Error messages - hide for Civitai since it's download-only */}
2067
  {installedError && provider !== 'civitai' ? (
2068
  <div className={`rounded-xl border p-5 ${
@@ -3061,6 +3079,8 @@ export default function ModelsView(props: ModelsParams) {
3061
  )}
3062
  </div>
3063
  )}
 
 
3064
  </div>
3065
  </div>
3066
 
 
588
 
589
  const [modelType, setModelType] = useState<'chat' | 'multimodal' | 'image' | 'video' | 'edit' | 'enhance' | 'addons' | 'lora'>('chat')
590
  const [provider, setProvider] = useState<string>(props.providerChat || 'ollama')
591
+ // Latest provider for effects that only depend on [modelType] but must not
592
+ // clobber a manual OllaBridge selection when the user switches tabs.
593
+ const providerRef = React.useRef(provider)
594
+ React.useEffect(() => { providerRef.current = provider }, [provider])
595
 
596
  const defaultBaseUrl = useMemo(() => {
597
  if (modelType === 'chat') return props.baseUrlChat || ''
 
664
 
665
  // Filter providers based on model type
666
  const availableProviders = useMemo(() => {
667
+ // OllaBridge is a provider *class* available under EVERY Model Type — its
668
+ // models come from the user's linked HomePilot / GPU nodes (relay-synced),
669
+ // so web & mobile can use remote compute. Listed first as the hero option.
670
+ const ollabridge: Provider = { name: 'ollabridge', label: '☁️ OllaBridge (your nodes)', kind: modelType as any }
671
  if (modelType === 'chat') {
672
  // For chat: all providers EXCEPT comfyui and civitai
673
+ return [ollabridge, ...providers.filter(p => p.name !== 'comfyui' && p.name !== 'civitai')]
674
  } else if (modelType === 'multimodal') {
675
+ // Multimodal: Ollama (vision models run locally) + OllaBridge nodes
676
+ return [ollabridge, ...providers.filter(p => p.name === 'ollama')]
677
  } else {
678
+ // For image/video/edit/enhance/lora/addons: comfyui + civitai (if experimental) + OllaBridge
679
  const filtered = providers.filter(p => p.name === 'comfyui')
680
 
681
  // Add Civitai only for image/video (Civitai API only supports these types)
 
687
  })
688
  }
689
 
690
+ return [ollabridge, ...filtered]
691
  }
692
  }, [providers, modelType, props.experimentalCivitai])
693
 
694
  useEffect(() => {
695
+ // Keep OllaBridge selected across Model Type tabs — it spans all types and
696
+ // its content is type-filtered, so don't reset it to a local provider.
697
+ if (providerRef.current === 'ollabridge') return
698
  // When modelType changes, update provider + baseUrl defaults
699
  if (modelType === 'chat') {
700
  // Switch to a chat provider (prefer the one from settings, or default to ollama)
 
773
  // Skip fetching installed models for remote API providers and Civitai
774
  // Remote providers: they're cloud services, not local installations
775
  // Civitai: download-only provider, models get installed to ComfyUI after download
776
+ // OllaBridge renders its own relay-synced list; the local backend has no
777
+ // catalog for it, so skip the fetch (avoids a spurious error banner).
778
+ const skipProviders = ['openai', 'claude', 'watsonx', 'civitai', 'ollabridge']
779
 
780
  if (skipProviders.includes(provider)) {
781
  setInstalled([])
 
2073
  {/* Body */}
2074
  <div className="flex-1 overflow-y-auto px-4 sm:px-8 py-6 scrollbar-hide">
2075
  <div className="flex flex-col gap-4 max-w-7xl mx-auto">
2076
+ {/* OllaBridge provider class — a special provider whose models are
2077
+ synced from the user's linked HomePilot / GPU nodes (relay), shown
2078
+ for whichever Model Type tab is active. When selected it replaces
2079
+ the local catalog for this tab. */}
2080
+ {provider === 'ollabridge' ? (
2081
+ <OllaBridgeModels filterType={modelType} />
2082
+ ) : (
2083
+ <>
2084
  {/* Error messages - hide for Civitai since it's download-only */}
2085
  {installedError && provider !== 'civitai' ? (
2086
  <div className={`rounded-xl border p-5 ${
 
3079
  )}
3080
  </div>
3081
  )}
3082
+ </>
3083
+ )}
3084
  </div>
3085
  </div>
3086
 
frontend/src/ui/components/OllaBridgeModels.tsx CHANGED
@@ -58,18 +58,39 @@ interface CloudModel {
58
  device_name?: string
59
  }
60
 
61
- /** Loose model-type guess from the model id — purely cosmetic badges. */
62
  function guessType(id: string): string {
63
  const s = id.toLowerCase()
64
  if (/(llava|vision|-vl|minicpm-v|moondream)/.test(s)) return 'multimodal'
65
  if (/(sdxl|flux|stable-diffusion|sd15|sd3|pony|dreamshaper)/.test(s)) return 'image'
66
  if (/(svd|ltx|wan|video)/.test(s)) return 'video'
67
- if (/(upscal|esrgan|enhance|restore)/.test(s)) return 'enhance'
68
  if (/lora/.test(s)) return 'LoRA'
69
  return 'chat'
70
  }
71
 
72
- export default function OllaBridgeModels() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  const cloudUrl = getCloudUrl()
74
  const [token, setToken] = useState(getCloudToken())
75
  const [devices, setDevices] = useState<DeviceInfo[]>([])
@@ -267,10 +288,22 @@ export default function OllaBridgeModels() {
267
  ))}
268
  </div>
269
 
270
- {/* Synced models */}
271
- {models.length > 0 && (
 
 
 
 
 
 
 
 
 
 
 
 
272
  <div className="grid gap-1.5">
273
- {models.slice(0, 30).map((m) => {
274
  const own = m.source === 'shared_device'
275
  const type = guessType(m.id)
276
  const isActive = activeModel === m.id
@@ -290,26 +323,28 @@ export default function OllaBridgeModels() {
290
  GPU node
291
  </span>
292
  )}
293
- <button
294
- type="button"
295
- onClick={() => useForChat(m.id)}
296
- className={[
297
- 'shrink-0 h-8 px-3 rounded-lg text-xs font-semibold inline-flex items-center gap-1.5 transition-colors',
298
- isActive
299
- ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30'
300
- : 'bg-white/5 hover:bg-white/10 border border-white/10 text-white/70 hover:text-white',
301
- ].join(' ')}
302
- >
303
- <Zap size={12} /> {isActive ? 'Active' : 'Use for Chat'}
304
- </button>
 
 
 
 
305
  </div>
306
  )
307
  })}
308
  </div>
309
- )}
310
- {!loading && token && models.length === 0 && devices.length > 0 && (
311
- <p className="text-xs text-white/40">No models advertised by your nodes yet — make sure the connector is running and models are pulled.</p>
312
- )}
313
  </>
314
  )}
315
  </section>
 
58
  device_name?: string
59
  }
60
 
61
+ /** Loose model-type guess from the model id — drives badges + per-tab filter. */
62
  function guessType(id: string): string {
63
  const s = id.toLowerCase()
64
  if (/(llava|vision|-vl|minicpm-v|moondream)/.test(s)) return 'multimodal'
65
  if (/(sdxl|flux|stable-diffusion|sd15|sd3|pony|dreamshaper)/.test(s)) return 'image'
66
  if (/(svd|ltx|wan|video)/.test(s)) return 'video'
67
+ if (/(upscal|esrgan|enhance|restore|gfpgan|codeformer)/.test(s)) return 'enhance'
68
  if (/lora/.test(s)) return 'LoRA'
69
  return 'chat'
70
  }
71
 
72
+ /** Does a model's guessed type belong under the given Model Type tab? */
73
+ function matchesTab(filterType: string | undefined, guessed: string): boolean {
74
+ if (!filterType) return true
75
+ switch (filterType) {
76
+ case 'chat': return guessed === 'chat'
77
+ case 'multimodal': return guessed === 'multimodal'
78
+ case 'image':
79
+ case 'edit': return guessed === 'image'
80
+ case 'video': return guessed === 'video'
81
+ case 'enhance': return guessed === 'enhance'
82
+ case 'lora': return guessed === 'LoRA'
83
+ case 'addons': return true
84
+ default: return true
85
+ }
86
+ }
87
+
88
+ const TAB_LABEL: Record<string, string> = {
89
+ chat: 'chat', multimodal: 'multimodal', image: 'image', edit: 'edit',
90
+ video: 'video', enhance: 'enhance', lora: 'LoRA', addons: 'add-on',
91
+ }
92
+
93
+ export default function OllaBridgeModels({ filterType }: { filterType?: string } = {}) {
94
  const cloudUrl = getCloudUrl()
95
  const [token, setToken] = useState(getCloudToken())
96
  const [devices, setDevices] = useState<DeviceInfo[]>([])
 
288
  ))}
289
  </div>
290
 
291
+ {/* Synced models — filtered to the active Model Type tab. */}
292
+ {(() => {
293
+ const shown = models.filter((m) => matchesTab(filterType, guessType(m.id)))
294
+ if (shown.length === 0) {
295
+ if (loading || devices.length === 0) return null
296
+ const lbl = filterType ? (TAB_LABEL[filterType] || filterType) : ''
297
+ return (
298
+ <p className="text-xs text-white/40 py-1">
299
+ No {lbl} models advertised by your nodes yet. Pull {lbl} models on your linked
300
+ HomePilot and they’ll sync here automatically.
301
+ </p>
302
+ )
303
+ }
304
+ return (
305
  <div className="grid gap-1.5">
306
+ {shown.slice(0, 40).map((m) => {
307
  const own = m.source === 'shared_device'
308
  const type = guessType(m.id)
309
  const isActive = activeModel === m.id
 
323
  GPU node
324
  </span>
325
  )}
326
+ {(type === 'chat' || type === 'multimodal') ? (
327
+ <button
328
+ type="button"
329
+ onClick={() => useForChat(m.id)}
330
+ className={[
331
+ 'shrink-0 h-8 px-3 rounded-lg text-xs font-semibold inline-flex items-center gap-1.5 transition-colors',
332
+ isActive
333
+ ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30'
334
+ : 'bg-white/5 hover:bg-white/10 border border-white/10 text-white/70 hover:text-white',
335
+ ].join(' ')}
336
+ >
337
+ <Zap size={12} /> {isActive ? 'Active' : 'Use for Chat'}
338
+ </button>
339
+ ) : (
340
+ <span className="shrink-0 text-[10px] text-white/40 px-2 py-1 rounded-md bg-white/5 border border-white/10">Synced</span>
341
+ )}
342
  </div>
343
  )
344
  })}
345
  </div>
346
+ )
347
+ })()}
 
 
348
  </>
349
  )}
350
  </section>