Skip to content

Commit 5d73043

Browse files
KBLLRclaude
andcommitted
feat(mlx): Add dynamic model registry with capability-based categories
- Add GET /api/mlx/registry endpoint to fetch model-zoo registry - Group models by capability (chat, vision, embedding, audio, image-gen, etc.) - Update PlannerSidebar to use dynamic MLX capability categories - Models fetched from /api/mlx/registry?capability=X - Supports filtering by capability type - Removes hardcoded model lists in favor of registry-driven approach 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fe89dfb commit 5d73043

File tree

2 files changed

+109
-26
lines changed

2 files changed

+109
-26
lines changed

server/routes/mlx.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,88 @@ router.get('/models', async (req, res) => {
264264
}
265265
});
266266

267+
/**
268+
* GET /api/mlx/registry
269+
* Fetch model-zoo registry grouped by capability
270+
*
271+
* Query params:
272+
* capability?: string (filter by capability: text-generation, vision, embedding, etc.)
273+
*
274+
* Response:
275+
* {
276+
* ok: true,
277+
* categories: { [capability]: models[] },
278+
* total: number,
279+
* voices: string[],
280+
* requestId: string
281+
* }
282+
*/
283+
router.get('/registry', async (req, res) => {
284+
const requestId = req.requestId;
285+
const { capability } = req.query;
286+
287+
try {
288+
const fs = await import('fs/promises');
289+
const path = await import('path');
290+
291+
// Read model-zoo registry from core-x root
292+
const registryPath = path.resolve(process.cwd(), '../../model-zoo/registry.json');
293+
let registry;
294+
295+
try {
296+
const data = await fs.readFile(registryPath, 'utf-8');
297+
registry = JSON.parse(data);
298+
} catch (readError) {
299+
// Try alternate path for avatar-labs registry
300+
const altPath = path.resolve(process.cwd(), '../avatar-labs/public/models/registry.json');
301+
const data = await fs.readFile(altPath, 'utf-8');
302+
registry = JSON.parse(data);
303+
}
304+
305+
// Group models by capability
306+
const models = registry.models || [];
307+
const categories = {};
308+
309+
for (const model of models) {
310+
const caps = model.capabilities || [];
311+
const type = model.type || 'unknown';
312+
313+
// Use primary capability or type
314+
const primaryCap = caps[0] || type;
315+
316+
if (capability && primaryCap !== capability) continue;
317+
318+
if (!categories[primaryCap]) {
319+
categories[primaryCap] = [];
320+
}
321+
322+
categories[primaryCap].push({
323+
id: model.id,
324+
type: model.type,
325+
capabilities: model.capabilities || [],
326+
description: model.description,
327+
tags: model.tags || []
328+
});
329+
}
330+
331+
res
332+
.set('X-Request-ID', requestId)
333+
.json({
334+
ok: true,
335+
categories,
336+
total: models.length,
337+
voices: registry.voices || [],
338+
requestId
339+
});
340+
} catch (error) {
341+
logger.error('[MLX] Registry fetch failed', { requestId, error: error.message });
342+
res.status(500).json({
343+
ok: false,
344+
error: error.message,
345+
categories: {},
346+
requestId
347+
});
348+
}
349+
});
350+
267351
export default router;

src/apps/planner/components/PlannerSidebar.jsx

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -86,33 +86,32 @@ function UniversityServicesList(connectedServices) {
8686
];
8787
}
8888

89-
function ModelProvidersList() {
90-
// Local MLX model providers by capability
91-
return [
92-
// Text Generation
93-
{ id: 'mlx-llm', label: 'MLX Text', kind: 'model-provider', icon: 'chat', connected: true,
94-
meta: { capability: 'text', models: ['gemma-3-4b-it', 'llama-3.1-8b', 'qwen2.5-7b', 'mistral-7b'] } },
95-
96-
// Vision & Multimodal
97-
{ id: 'mlx-vision', label: 'MLX Vision', kind: 'model-provider', icon: 'visibility', connected: true,
98-
meta: { capability: 'vision', models: ['llava-1.5-7b', 'qwen-vl-chat'] } },
99-
100-
// Embeddings
101-
{ id: 'mlx-embed', label: 'MLX Embed', kind: 'model-provider', icon: 'hub', connected: true,
102-
meta: { capability: 'embeddings', models: ['all-MiniLM-L6-v2', 'bge-small-en'] } },
103-
104-
// Speech-to-Text
105-
{ id: 'mlx-whisper', label: 'MLX Whisper', kind: 'model-provider', icon: 'mic', connected: true,
106-
meta: { capability: 'stt', models: ['whisper-large-v3', 'whisper-medium'] } },
107-
108-
// Text-to-Speech
109-
{ id: 'mlx-tts', label: 'MLX TTS', kind: 'model-provider', icon: 'record_voice_over', connected: true,
110-
meta: { capability: 'tts', models: ['kokoro-82m'] } },
89+
// MLX capability categories - models fetched dynamically from /api/mlx/registry
90+
const MLX_CAPABILITIES = [
91+
{ id: 'chat', capability: 'chat', label: 'Text Generation', icon: 'chat' },
92+
{ id: 'streaming', capability: 'streaming', label: 'Streaming LLM', icon: 'stream' },
93+
{ id: 'vision', capability: 'vision', label: 'Vision', icon: 'visibility' },
94+
{ id: 'embedding', capability: 'embedding', label: 'Embeddings', icon: 'hub' },
95+
{ id: 'audio-transcribe', capability: 'audio-transcribe', label: 'Speech-to-Text', icon: 'mic' },
96+
{ id: 'audio-generate', capability: 'audio-generate', label: 'Text-to-Speech', icon: 'record_voice_over' },
97+
{ id: 'image-generation', capability: 'image-generation', label: 'Image Generation', icon: 'image' },
98+
{ id: 'voice-cloning', capability: 'voice-cloning', label: 'Voice Cloning', icon: 'voice_over_off' },
99+
];
111100

112-
// Image Generation
113-
{ id: 'mlx-diffusion', label: 'MLX Diffusion', kind: 'model-provider', icon: 'image', connected: true,
114-
meta: { capability: 'image-gen', models: ['sdxl-turbo', 'flux-schnell'] } },
115-
];
101+
function ModelProvidersList() {
102+
// MLX model categories - fetches from /api/mlx/registry?capability=X
103+
return MLX_CAPABILITIES.map(cap => ({
104+
id: `mlx-${cap.id}`,
105+
label: cap.label,
106+
kind: 'model-provider',
107+
icon: cap.icon,
108+
connected: true,
109+
meta: {
110+
capability: cap.capability,
111+
endpoint: `/api/mlx/registry?capability=${cap.capability}`,
112+
dynamic: true // indicates models should be fetched
113+
}
114+
}));
116115
}
117116

118117
function WorkflowsList(customWorkflows) {

0 commit comments

Comments
 (0)