165 lines
7.3 KiB
JavaScript
165 lines
7.3 KiB
JavaScript
/**
|
|
* Shared QVAC chat catalog + REST /models helpers (preamble for agent and discord-bot).
|
|
*/
|
|
|
|
/** @type {{ id: string, family: string, label: string, tools: boolean, ramGb: number, profile?: string }[]} */
|
|
var BARE_AGENT_QVAC_CHAT_MODELS = [
|
|
{ id: 'QWEN3_600M_INST_Q4', family: 'qwen3', label: 'Qwen3 0.6B Instruct Q4', tools: true, ramGb: 4, profile: 'lite' },
|
|
{ id: 'QWEN3_1_7B_INST_Q4', family: 'qwen3', label: 'Qwen3 1.7B Instruct Q4', tools: true, ramGb: 8, profile: 'recommended' },
|
|
{ id: 'QWEN3_4B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Instruct Q4_K_M', tools: true, ramGb: 16, profile: 'strong' },
|
|
{ id: 'QWEN3_4B_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Q4_K_M', tools: true, ramGb: 16 },
|
|
{ id: 'QWEN3_8B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 8B Instruct Q4_K_M', tools: true, ramGb: 24 },
|
|
{ id: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K', family: 'llama', label: 'Llama 3.2 1B tool-calling', tools: true, ramGb: 6, profile: 'tool-tiny' },
|
|
{ id: 'LLAMA_3_2_1B_INST_Q4_0', family: 'llama', label: 'Llama 3.2 1B Instruct Q4_0', tools: true, ramGb: 6 },
|
|
{ id: 'SMOLLM2_360M_INST_Q8', family: 'smol', label: 'SmolLM2 360M Instruct Q8', tools: false, ramGb: 3 },
|
|
{ id: 'GPT_OSS_20B_INST_Q4_K_M', family: 'gpt-oss', label: 'GPT-OSS 20B Instruct Q4_K_M', tools: true, ramGb: 24 },
|
|
{ id: 'GEMMA4_2B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 2B multimodal Q4', tools: true, ramGb: 8 },
|
|
{ id: 'GEMMA4_2B_MULTIMODAL_Q6_K', family: 'gemma', label: 'Gemma 4 2B multimodal Q6', tools: true, ramGb: 10 },
|
|
{ id: 'GEMMA4_4B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 4B multimodal Q4', tools: true, ramGb: 16 },
|
|
{ id: 'GEMMA4_31B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 31B multimodal Q4', tools: true, ramGb: 48 },
|
|
{ id: 'QWEN3VL_2B_MULTIMODAL_Q4_K', family: 'qwen3', label: 'Qwen3-VL 2B multimodal Q4', tools: true, ramGb: 10 },
|
|
{ id: 'QWEN3_5_2B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 2B multimodal Q4', tools: true, ramGb: 10 },
|
|
{ id: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 4B multimodal Q4', tools: true, ramGb: 16 },
|
|
{ id: 'QWEN3_5_0_8B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 8B multimodal Q4', tools: true, ramGb: 24 },
|
|
{ id: 'QWEN3_5_9B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 9B multimodal Q4', tools: true, ramGb: 28 },
|
|
{ id: 'QWEN3_6_27B_MULTIMODAL_Q4_K_XL', family: 'large', label: 'Qwen3.6 27B multimodal Q4', tools: true, ramGb: 48 }
|
|
]
|
|
|
|
/** @type {Record<string, string[]>} */
|
|
var BARE_AGENT_REST_MODEL_FALLBACKS = {
|
|
groq: [
|
|
'llama-3.3-70b-versatile',
|
|
'llama-3.1-8b-instant',
|
|
'openai/gpt-oss-120b',
|
|
'openai/gpt-oss-20b',
|
|
'qwen/qwen3-32b',
|
|
'moonshotai/kimi-k2-instruct',
|
|
'meta-llama/llama-4-scout-17b-16e-instruct',
|
|
'meta-llama/llama-4-maverick-17b-128e-instruct',
|
|
'groq/compound'
|
|
],
|
|
xai: ['grok-4', 'grok-3', 'grok-3-mini', 'grok-3-fast', 'grok-2-1212', 'grok-2-vision-1212'],
|
|
openai: ['gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4o', 'gpt-4o-mini', 'o4-mini', 'o3'],
|
|
custom: []
|
|
}
|
|
|
|
/**
|
|
* @param {string} [profileId]
|
|
*/
|
|
function bareAgentQvacModelForProfile(profileId) {
|
|
const id = String(profileId || '').trim().toLowerCase()
|
|
for (let i = 0; i < BARE_AGENT_QVAC_CHAT_MODELS.length; i++) {
|
|
if (BARE_AGENT_QVAC_CHAT_MODELS[i].profile === id) return BARE_AGENT_QVAC_CHAT_MODELS[i]
|
|
}
|
|
return BARE_AGENT_QVAC_CHAT_MODELS[1] || BARE_AGENT_QVAC_CHAT_MODELS[0]
|
|
}
|
|
|
|
/**
|
|
* @param {string} [modelId]
|
|
*/
|
|
function bareAgentQvacFindChatModel(modelId) {
|
|
const id = String(modelId || '').trim()
|
|
for (let i = 0; i < BARE_AGENT_QVAC_CHAT_MODELS.length; i++) {
|
|
if (BARE_AGENT_QVAC_CHAT_MODELS[i].id === id) return BARE_AGENT_QVAC_CHAT_MODELS[i]
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* @param {{ id: string, label?: string, family?: string }[]} models
|
|
* @param {{ family?: string, query?: string }} [opts]
|
|
*/
|
|
function bareAgentFilterModelList(models, opts) {
|
|
const o = opts && typeof opts === 'object' ? opts : {}
|
|
const fam = String(o.family || 'all').trim().toLowerCase()
|
|
const q = String(o.query || '').trim().toLowerCase()
|
|
const rows = Array.isArray(models) ? models : []
|
|
/** @type {{ id: string, label?: string, family?: string }[]} */
|
|
const out = []
|
|
for (let i = 0; i < rows.length; i++) {
|
|
const m = rows[i]
|
|
if (!m || !m.id) continue
|
|
if (fam && fam !== 'all' && String(m.family || '').toLowerCase() !== fam) continue
|
|
if (q) {
|
|
const blob = (String(m.id) + ' ' + String(m.label || '')).toLowerCase()
|
|
if (blob.indexOf(q) === -1) continue
|
|
}
|
|
out.push(m)
|
|
}
|
|
return out
|
|
}
|
|
|
|
/**
|
|
* @param {unknown} json
|
|
* @returns {{ id: string, label: string, family: string, owned_by: string }[]}
|
|
*/
|
|
function bareAgentParseOpenAiModels(json) {
|
|
const raw =
|
|
json && typeof json === 'object' && Array.isArray(/** @type {{ data?: unknown }} */ (json).data)
|
|
? /** @type {{ data: unknown[] }} */ (json).data
|
|
: Array.isArray(json)
|
|
? json
|
|
: []
|
|
const skip = /embed|whisper|tts|dall-e|davinci|babbage|audio|moderation|realtime|image|sora/i
|
|
/** @type {{ id: string, label: string, family: string, owned_by: string }[]} */
|
|
const out = []
|
|
const seen = Object.create(null)
|
|
for (let i = 0; i < raw.length; i++) {
|
|
const row = raw[i] && typeof raw[i] === 'object' ? /** @type {Record<string, unknown>} */ (raw[i]) : null
|
|
if (!row) continue
|
|
const id = String(row.id || row.name || '').trim()
|
|
if (!id || seen[id] || skip.test(id)) continue
|
|
seen[id] = 1
|
|
const owned = String(row.owned_by || row.ownedBy || '')
|
|
out.push({
|
|
id: id,
|
|
label: id,
|
|
family: owned || 'api',
|
|
owned_by: owned
|
|
})
|
|
}
|
|
out.sort(function (a, b) {
|
|
return a.id.localeCompare(b.id)
|
|
})
|
|
return out
|
|
}
|
|
|
|
/**
|
|
* @param {string} [provider]
|
|
*/
|
|
function bareAgentRestModelsFallback(provider) {
|
|
const key = String(provider || 'groq').trim().toLowerCase()
|
|
const ids = BARE_AGENT_REST_MODEL_FALLBACKS[key] || BARE_AGENT_REST_MODEL_FALLBACKS.groq
|
|
return (ids || []).map(function (id) {
|
|
return { id: id, label: id, family: key, owned_by: key }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @param {(url: string, init?: object) => Promise<{ ok?: boolean, status?: number, json?: () => Promise<unknown>, text?: () => Promise<string> }>} fetchFn
|
|
* @param {{ baseUrl?: string, apiKey?: string }} opts
|
|
*/
|
|
async function bareAgentFetchRestModels(fetchFn, opts) {
|
|
const o = opts && typeof opts === 'object' ? opts : {}
|
|
const base = String(o.baseUrl || '').trim().replace(/\/+$/, '')
|
|
if (!base) throw new Error('rest_base_url required')
|
|
if (typeof fetchFn !== 'function') throw new Error('fetch unavailable')
|
|
const url = base + '/models'
|
|
/** @type {Record<string, string>} */
|
|
const headers = { Accept: 'application/json' }
|
|
const key = String(o.apiKey || '').trim()
|
|
if (key) headers.Authorization = 'Bearer ' + key
|
|
const res = await fetchFn(url, { method: 'GET', headers: headers })
|
|
if (!res || res.ok === false) {
|
|
const st = res && res.status != null ? String(res.status) : 'fetch_failed'
|
|
throw new Error('models_http_' + st)
|
|
}
|
|
let json
|
|
if (typeof res.json === 'function') json = await res.json()
|
|
else if (typeof res.text === 'function') json = JSON.parse(await res.text())
|
|
else throw new Error('models_unreadable')
|
|
const list = bareAgentParseOpenAiModels(json)
|
|
if (!list.length) throw new Error('models_empty')
|
|
return list
|
|
}
|