Files
iqAI/backend/routes/models.js
Malin 71965939a1 feat: initial IQAI multi-model AI dashboard
- Express backend with Replicate API proxy (chat, models, account, search)
- React + Vite + Tailwind frontend with custom Midnight Violet color scheme
- @mention autocomplete to route messages to specific models
- Parallel multi-model queries with model selection in sidebar
- DuckDuckGo web search context injection
- Model manager UI (add/edit/remove Replicate models)
- Per-model system instructions per conversation
- Replicate account info display in sidebar
- Conversation history with local persistence (Zustand)
- Full Docker deployment (backend + nginx-served frontend)
- Montserrat + Poppins fonts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:12:40 +02:00

85 lines
2.7 KiB
JavaScript

import express from 'express';
import { readFile, writeFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const router = express.Router();
const __dirname = dirname(fileURLToPath(import.meta.url));
const MODELS_PATH = join(__dirname, '../data/models.json');
async function loadModels() {
const raw = await readFile(MODELS_PATH, 'utf-8');
return JSON.parse(raw);
}
async function saveModels(models) {
await writeFile(MODELS_PATH, JSON.stringify(models, null, 2));
}
router.get('/', async (req, res) => {
try {
const models = await loadModels();
res.json(models);
} catch (err) {
res.status(500).json({ error: 'Failed to load models' });
}
});
router.post('/', async (req, res) => {
try {
const { tag, displayName, owner, name, type, avatar, color, description, systemPromptParam, defaultInput } = req.body;
if (!tag || !owner || !name || !displayName) {
return res.status(400).json({ error: 'Missing required fields: tag, owner, name, displayName' });
}
const models = await loadModels();
if (models.find(m => m.tag === tag)) {
return res.status(409).json({ error: `Tag @${tag} already exists` });
}
const newModel = {
id: `${owner}-${name}`.replace(/[^a-z0-9-]/gi, '-'),
tag: tag.toLowerCase().replace(/[^a-z0-9]/g, ''),
displayName,
owner,
name,
type: type || 'text',
avatar: avatar || '🤖',
color: color || '#6B7280',
description: description || '',
systemPromptParam: systemPromptParam || null,
defaultInput: defaultInput || {}
};
models.push(newModel);
await saveModels(models);
res.status(201).json(newModel);
} catch (err) {
res.status(500).json({ error: 'Failed to add model' });
}
});
router.put('/:id', async (req, res) => {
try {
const models = await loadModels();
const idx = models.findIndex(m => m.id === req.params.id);
if (idx === -1) return res.status(404).json({ error: 'Model not found' });
models[idx] = { ...models[idx], ...req.body, id: models[idx].id };
await saveModels(models);
res.json(models[idx]);
} catch (err) {
res.status(500).json({ error: 'Failed to update model' });
}
});
router.delete('/:id', async (req, res) => {
try {
const models = await loadModels();
const filtered = models.filter(m => m.id !== req.params.id);
if (filtered.length === models.length) return res.status(404).json({ error: 'Model not found' });
await saveModels(filtered);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: 'Failed to delete model' });
}
});
export { router as modelsRouter, loadModels };