feat(ai): commandes @/ & skills, analyse d'images et capacites des modeles (#81)
CI / lint (push) Successful in 1m10s
CI / security (push) Successful in 43s
CI / test (push) Successful in 2m34s
CI / build (push) Successful in 43s
CI / e2e (push) Successful in 10m59s

This commit is contained in:
2026-09-12 11:38:46 -04:00
parent 88bb817a62
commit f049e208b6
25 changed files with 2581 additions and 31 deletions
+65
View File
@@ -33,6 +33,12 @@ async function aiAction(endpoint, text, extra = {}) {
// State is stored in localStorage so the user choice persists across sections.
const PICKER_STORAGE_KEY = 'obsigate_ai_picker';
// Capability flags exposed by GET /api/ai/model-capabilities.
const AI_CAPABILITY_KEYS = [
'chat', 'embeddings', 'rerank', 'images',
'video', 'audio_speech', 'audio_transcription', 'vision',
];
function _readPicker() {
try {
return JSON.parse(localStorage.getItem(PICKER_STORAGE_KEY) || '{}');
@@ -45,6 +51,32 @@ function _writePicker(state) {
} catch { /* */ }
}
/** Fetch the curated capability flags for a provider/model pair. */
async function getModelCapabilities(provider, model) {
if (!provider) return null;
try {
const data = await api(
`/api/ai/model-capabilities?provider=${encodeURIComponent(provider)}&model=${encodeURIComponent(model || '')}`
);
return data.capabilities || null;
} catch { return null; }
}
/** Render the capability checklist (☑/□) for a model. */
function renderCapabilityList(caps) {
const box = document.createElement('div');
box.className = 'ai-picker-caps';
if (!caps) return box;
AI_CAPABILITY_KEYS.forEach((key) => {
const item = document.createElement('span');
const on = !!caps[key];
item.className = 'ai-cap-item' + (on ? ' on' : '');
item.textContent = `${on ? '☑' : '□'} ${t(`ai.cap_${key}`)}`;
box.appendChild(item);
});
return box;
}
/**
* Build a provider+model picker that appears in every section's AI toolbar
* (Forge editor, BooksLM, etc.). The picker reads /api/ai/status to discover
@@ -128,6 +160,24 @@ async function _buildPickerUI() {
placeholderOpt.textContent = t('ai.model_default');
modelSelect.appendChild(placeholderOpt);
// Capability checklist, refreshed whenever the selected model changes.
const capsHost = document.createElement('div');
capsHost.className = 'ai-picker-caps-host';
async function _updateCaps() {
capsHost.innerHTML = '';
if (!pickerState.provider || !pickerState.model) {
delete pickerState.capabilities;
_writePicker(pickerState);
return;
}
const caps = await getModelCapabilities(pickerState.provider, pickerState.model);
if (!caps) return;
pickerState.capabilities = caps;
_writePicker(pickerState);
capsHost.appendChild(renderCapabilityList(caps));
}
async function _loadModels(provider) {
modelSelect.innerHTML = '';
const ph = document.createElement('option');
@@ -148,6 +198,13 @@ async function _buildPickerUI() {
if (m === pickerState.model && provider === pickerState.provider) opt.selected = true;
modelSelect.appendChild(opt);
});
// If the provider default model is known, reflect it so capabilities
// are shown even when the user keeps the "default" placeholder.
if (!pickerState.model && data.models && data.models.length) {
pickerState.model = data.models[0];
_writePicker(pickerState);
}
await _updateCaps();
} catch (err) {
modelSelect.innerHTML = '';
const opt = document.createElement('option');
@@ -161,7 +218,9 @@ async function _buildPickerUI() {
pickerState.provider = providerSelect.value || null;
// Reset model when provider changes
pickerState.model = null;
delete pickerState.capabilities;
_writePicker(pickerState);
capsHost.innerHTML = '';
if (pickerState.provider) {
_loadModels(pickerState.provider);
} else {
@@ -175,7 +234,9 @@ async function _buildPickerUI() {
modelSelect.addEventListener('change', () => {
pickerState.model = modelSelect.value || null;
delete pickerState.capabilities;
_writePicker(pickerState);
_updateCaps();
});
// Load models for the initial provider selection
@@ -185,6 +246,7 @@ async function _buildPickerUI() {
wrap.appendChild(providerSelect);
wrap.appendChild(modelSelect);
wrap.appendChild(capsHost);
return wrap;
}
@@ -712,6 +774,9 @@ export {
_readPicker,
_writePicker,
PICKER_STORAGE_KEY,
AI_CAPABILITY_KEYS,
getModelCapabilities,
renderCapabilityList,
_buildPickerUI as buildAIPickerUI,
_promptRewriteInstruction,
};