First Try: QVAC (QuantumVerse Automatic Computer)
This commit is contained in:
+139
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Lightweight local RAG for QVAC — no native embeddings required.
|
||||
* Keyword retrieval over guide snippets + live chart catalog.
|
||||
*/
|
||||
|
||||
/** Built-in operator knowledge (subset of user-guide). */
|
||||
const GUIDE_DOCS = [
|
||||
{
|
||||
id: 'guide:connect',
|
||||
title: 'Connect',
|
||||
text: 'Connect to a PearData agent with public key or pd1 invite. Viewer vs operator vs admin roles. Saved bookmarks restore agents. Active peer is used by all tools.',
|
||||
},
|
||||
{
|
||||
id: 'guide:charts',
|
||||
title: 'Charts',
|
||||
text: 'Metrics wall with sections TOC, play pause, time presets 1m 5m 15m 1h 6h, pin board, correlate brush, related metrics, chart types line area stacked bar pie, dimension legend.',
|
||||
},
|
||||
{
|
||||
id: 'guide:alerts',
|
||||
title: 'Alerts',
|
||||
text: 'Anomaly list with severity warning critical, Show jumps to chart, Correlate opens metric correlations around event time, silence TTL re-enables.',
|
||||
},
|
||||
{
|
||||
id: 'guide:processes',
|
||||
title: 'Processes',
|
||||
text: 'Live process table from /proc when PEARDATA_PROCESSES=1, sort by cpu rss, tree view, CSV export, open top process charts.',
|
||||
},
|
||||
{
|
||||
id: 'guide:logs',
|
||||
title: 'Logs',
|
||||
text: 'System logs journal default, anomalies source, audit admin, follow stream, search unit priority.',
|
||||
},
|
||||
{
|
||||
id: 'guide:retention',
|
||||
title: 'Data retention',
|
||||
text: 'Settings Data Manager hot tier0 warm tier1 HyperDB prune age disk budget. Longer chart windows may be sparse until samples accumulate.',
|
||||
},
|
||||
{
|
||||
id: 'guide:qvac',
|
||||
title: 'QVAC',
|
||||
text: 'Local AI copilot on desktop. Tools call agent RPC never invent metrics. Profiles lite recommended strong. Install @qvac/sdk for full LLM else tools-only fallback.',
|
||||
},
|
||||
{
|
||||
id: 'guide:fleet',
|
||||
title: 'Fleet',
|
||||
text: 'Multi-host roster set active reconnect forget. Parent collector aggregates child getHealth. getFleetHealth RPC.',
|
||||
},
|
||||
{
|
||||
id: 'guide:kpis',
|
||||
title: 'Core KPIs',
|
||||
text: 'system.cpu user system idle iowait, system.ram used, system.load load1, system.net received sent, system.io reads writes, mem.available.',
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function tokens(text) {
|
||||
return String(text || '')
|
||||
.toLowerCase()
|
||||
.split(/[^a-z0-9._-]+/)
|
||||
.filter((t) => t.length > 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build docs from chart catalog map.
|
||||
* @param {Record<string, object>} catalog
|
||||
* @param {number} [cap]
|
||||
*/
|
||||
export function catalogDocs(catalog, cap = 400) {
|
||||
/** @type {Array<{ id: string, title: string, text: string }>} */
|
||||
const docs = []
|
||||
for (const [id, meta] of Object.entries(catalog || {})) {
|
||||
if (docs.length >= cap) break
|
||||
const dims = Array.isArray(meta.dimensions)
|
||||
? meta.dimensions
|
||||
.map((d) => (typeof d === 'string' ? d : d?.id || d?.name || ''))
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
: ''
|
||||
docs.push({
|
||||
id: `chart:${id}`,
|
||||
title: meta.title || id,
|
||||
text: [id, meta.title, meta.context, meta.family, meta.plugin, meta.units, dims]
|
||||
.filter(Boolean)
|
||||
.join(' '),
|
||||
})
|
||||
}
|
||||
return docs
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {Array<{ id: string, title: string, text: string }>} docs
|
||||
* @param {number} [topK]
|
||||
*/
|
||||
export function retrieve(query, docs, topK = 6) {
|
||||
const qToks = tokens(query)
|
||||
if (!qToks.length || !docs?.length) return []
|
||||
/** @type {Array<{ id: string, title: string, text: string, score: number }>} */
|
||||
const scored = []
|
||||
for (const d of docs) {
|
||||
const hay = tokens(`${d.title} ${d.text}`)
|
||||
const set = new Set(hay)
|
||||
let score = 0
|
||||
for (const t of qToks) {
|
||||
if (set.has(t)) score += 2
|
||||
else if (hay.some((h) => h.includes(t) || t.includes(h))) score += 1
|
||||
}
|
||||
if (d.id.startsWith('chart:') && qToks.some((t) => d.id.includes(t))) score += 3
|
||||
if (score > 0) scored.push({ ...d, score })
|
||||
}
|
||||
scored.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
|
||||
return scored.slice(0, topK)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* query: string,
|
||||
* catalog?: Record<string, object>,
|
||||
* topK?: number,
|
||||
* includeGuide?: boolean,
|
||||
* }} opts
|
||||
*/
|
||||
export function buildRagContext(opts) {
|
||||
const docs = []
|
||||
if (opts.includeGuide !== false) docs.push(...GUIDE_DOCS)
|
||||
if (opts.catalog) docs.push(...catalogDocs(opts.catalog))
|
||||
const hits = retrieve(opts.query, docs, opts.topK ?? 6)
|
||||
if (!hits.length) return ''
|
||||
const lines = hits.map((h) => {
|
||||
const snippet = h.text.length > 180 ? h.text.slice(0, 177) + '…' : h.text
|
||||
return `- [${h.id}] ${h.title}: ${snippet}`
|
||||
})
|
||||
return `Relevant local knowledge (do not invent beyond tools + this context):\n${lines.join('\n')}`
|
||||
}
|
||||
|
||||
export { GUIDE_DOCS }
|
||||
Reference in New Issue
Block a user