implementing model selector
This commit is contained in:
@@ -138,7 +138,7 @@ function NumberSetting({ label, description, value, min, max, step = 1, onSave,
|
|||||||
function SettingsRow({ label, description, action }) {
|
function SettingsRow({ label, description, action }) {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between',
|
||||||
padding: '14px 16px',
|
padding: '14px 16px',
|
||||||
borderBottom: '1px solid var(--border)',
|
borderBottom: '1px solid var(--border)',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ const { ORCHESTRATION } = require('@nexusai/shared');
|
|||||||
const SETTINGS_PATH = path.join(__dirname, '../../data/settings.json');
|
const SETTINGS_PATH = path.join(__dirname, '../../data/settings.json');
|
||||||
|
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
recentEpisodeLimit: ORCHESTRATION.RECENT_EPISODE_LIMIT,
|
recentEpisodeLimit: ORCHESTRATION.RECENT_EPISODE_LIMIT,
|
||||||
semanticLimit: ORCHESTRATION.SEMANTIC_LIMIT,
|
semanticLimit: ORCHESTRATION.SEMANTIC_LIMIT,
|
||||||
scoreThreshold: ORCHESTRATION.SCORE_THRESHOLD,
|
scoreThreshold: ORCHESTRATION.SCORE_THRESHOLD,
|
||||||
|
modelsFolderPath: getEnv('MODELS_MANIFEST_PATH', '/mnt/nexus-models')
|
||||||
};
|
};
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
|
|||||||
@@ -1,21 +1,52 @@
|
|||||||
// routes/models.js
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const {getEnv} = require('@nexusai/shared');
|
const appSettings = require('../config/settings');
|
||||||
|
|
||||||
const MODELS_PATH = getEnv('MODELS_MANIFEST_PATH', path.join(__dirname, '../models.json'));
|
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
|
const { modelsFolderPath } = appSettings.load();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw = fs.readFileSync(MODELS_PATH, 'utf8');
|
// Try scanning folder for .gguf files
|
||||||
const models = JSON.parse(raw);
|
const files = fs.readdirSync(modelsFolderPath)
|
||||||
|
.filter(f => f.endsWith('.gguf'));
|
||||||
|
|
||||||
|
// Try loading models.json for richer metadata (label, description)
|
||||||
|
let manifest = {};
|
||||||
|
try {
|
||||||
|
const manifestPath = path.join(modelsFolderPath, 'models.json');
|
||||||
|
const raw = fs.readFileSync(manifestPath, 'utf8');
|
||||||
|
// Index manifest by filename for quick lookup
|
||||||
|
const list = JSON.parse(raw);
|
||||||
|
for (const m of list) {
|
||||||
|
manifest[m.value] = m;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// No manifest — scan only, that's fine
|
||||||
|
}
|
||||||
|
|
||||||
|
const models = files.map(filename => ({
|
||||||
|
value: filename,
|
||||||
|
label: manifest[filename]?.label ?? filename.replace('.gguf', ''),
|
||||||
|
description: manifest[filename]?.description ?? null,
|
||||||
|
size: getFileSizeMB(path.join(modelsFolderPath, filename)),
|
||||||
|
}));
|
||||||
|
|
||||||
res.json(models);
|
res.json(models);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[models] Failed to read manifest:', err.message);
|
console.error('[models] Failed to scan folder:', err.message);
|
||||||
res.status(500).json({ error: 'Could not load models manifest' });
|
res.status(500).json({ error: `Could not read models folder: ${modelsFolderPath}` });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getFileSizeMB(filepath) {
|
||||||
|
try {
|
||||||
|
const bytes = fs.statSync(filepath).size;
|
||||||
|
return (bytes / (1024 ** 3)).toFixed(1) + ' GB'; // models are big — show GB
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
const { Router } = require('express');
|
const { Router } = require('express');
|
||||||
const settings = require('../config/settings');
|
const settings = require('../config/settings');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -32,6 +33,18 @@ router.patch('/', (req, res) => {
|
|||||||
updates.scoreThreshold = val;
|
updates.scoreThreshold = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.body.modelsFolderPath !== undefined) {
|
||||||
|
const val = req.body.modelsFolderPath.trim();
|
||||||
|
if (!val) return res.status(400).json({ error: 'modelsFolderPath cannot be empty' });
|
||||||
|
// Verify the path exists and is readable
|
||||||
|
try {
|
||||||
|
fs.readdirSync(val);
|
||||||
|
} catch {
|
||||||
|
return res.status(400).json({ error: `Path not accessible: ${val}` });
|
||||||
|
}
|
||||||
|
updates.modelsFolderPath = val;
|
||||||
|
}
|
||||||
|
|
||||||
res.json(settings.save(updates));
|
res.json(settings.save(updates));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user