feat(ai): commandes @/ & skills, analyse d'images et capacites des modeles (#81)
This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user