CMMS fork: AI Assistant editor-buffer bridge (pcb kind)
cmms-bridge.js gains a window 'message' listener answering the CMMS AI
assistant's cmms-ai-editor-* protocol (same role as cad-editor's Rust
listener, since studio.html is the top-level page here):
- cmms-ai-editor-get-buffer -> replies { type:'cmms-ai-editor-buffer',
buffer: <JSON> } — device/board name, placed components
({label,value,desc,x,y}), nets ({name,pins:[...]}), and the imported
board-photo list.
- cmms-ai-editor-apply (editor:'pcb') -> parses a JSON patch of proposed
components + nets and merges it into the open project (upsert components
by label, nets by name; new parts with no coordinate are staggered so
they're draggable; net pins inherit their component's position if it
exists). Refreshes the BOM list / map / nets view. Nothing is saved -
the user checks each against the board photo and clicks Save.
Inert unless window.PCBRETRACE_CONFIG is set.
This commit is contained in:
parent
8bf5fd3a9a
commit
c15ba490b2
1 changed files with 89 additions and 0 deletions
|
|
@ -111,6 +111,95 @@
|
|||
}
|
||||
}
|
||||
|
||||
// ---- AI Assistant editor-buffer bridge -------------------------------
|
||||
// The chat embed (an iframe) postMessages here; this top-level page is the
|
||||
// "editor" side of the cmms-ai-editor-* protocol (like cad-editor's Rust
|
||||
// listener). read: serialise the open board; apply: merge a JSON patch of
|
||||
// proposed components + nets. Nothing is saved.
|
||||
|
||||
async function buildProjectView() {
|
||||
if (!currentDeviceId || !currentBomId) return null;
|
||||
const dev = deviceList.find(d => d.id === currentDeviceId);
|
||||
const board = bomList.find(b => b.id === currentBomId);
|
||||
const allNets = await db.getNets();
|
||||
const nets = allNets.filter(n => n.projectId === currentBomId);
|
||||
return JSON.stringify({
|
||||
device: dev ? dev.name : null,
|
||||
board: board ? board.name : null,
|
||||
image_count: bomImages.length,
|
||||
images: bomImages.map(i => ({ id: i.id, name: i.name || '' })),
|
||||
components: bomData.map(c => ({
|
||||
label: c.label, value: c.value || '', desc: c.desc || '',
|
||||
x: Math.round(c.x), y: Math.round(c.y),
|
||||
})),
|
||||
nets: nets.map(n => ({ name: n.name, pins: (n.nodes || []).map(nd => nd.label) })),
|
||||
});
|
||||
}
|
||||
|
||||
async function applyProjectPatch(patchJson) {
|
||||
const patch = JSON.parse(patchJson);
|
||||
if (!currentBomId) { status('Select or create a board first', true); return; }
|
||||
|
||||
const byLabel = l => bomData.find(c => (c.label || '').toUpperCase() === l.toUpperCase());
|
||||
const fallbackImg = currentImgId || (bomImages[0] && bomImages[0].id) || null;
|
||||
let spread = 40;
|
||||
|
||||
for (const c of (patch.components || [])) {
|
||||
const existing = byLabel(c.label);
|
||||
const rec = existing || { id: uuid(), boardId: currentBomId, label: c.label, x: 0, y: 0 };
|
||||
if (c.value !== undefined) rec.value = c.value;
|
||||
if (c.desc !== undefined) rec.desc = c.desc;
|
||||
if (c.x !== undefined) rec.x = Math.round(c.x);
|
||||
if (c.y !== undefined) rec.y = Math.round(c.y);
|
||||
// New part with no coordinate → stagger it so it's draggable, not stacked.
|
||||
if (!existing && c.x === undefined) { rec.x = spread; rec.y = spread; spread += 40; }
|
||||
rec.source = 'ai_suggested';
|
||||
await db.addComponent(rec);
|
||||
}
|
||||
|
||||
const allNets = await db.getNets();
|
||||
const projectNets = allNets.filter(n => n.projectId === currentBomId);
|
||||
for (const n of (patch.nets || [])) {
|
||||
let net = projectNets.find(x => x.name === n.name);
|
||||
if (!net) net = { id: uuid(), name: n.name, projectId: currentBomId, nodes: [] };
|
||||
const have = new Set((net.nodes || []).map(nd => nd.label));
|
||||
for (const pin of (n.pins || [])) {
|
||||
if (have.has(pin)) continue;
|
||||
const comp = byLabel(pin.split('.')[0]);
|
||||
net.nodes.push({
|
||||
id: uuid(),
|
||||
imgId: fallbackImg,
|
||||
x: comp ? Math.round(comp.x) : 0,
|
||||
y: comp ? Math.round(comp.y) : 0,
|
||||
label: pin,
|
||||
});
|
||||
}
|
||||
await db.addNet(net);
|
||||
}
|
||||
|
||||
await loadProjectData();
|
||||
if (window.netManager) await window.netManager.render();
|
||||
if (window.inspector && window.inspector.updateNetNodeCache) window.inspector.updateNetNodeCache();
|
||||
const nc = (patch.components || []).length, nn = (patch.nets || []).length;
|
||||
status(`AI proposal applied (${nc} component${nc === 1 ? '' : 's'}, ${nn} net${nn === 1 ? '' : 's'}) — check each against the photo, then Save`);
|
||||
}
|
||||
|
||||
window.addEventListener('message', async (evt) => {
|
||||
if (evt.origin !== window.location.origin || !evt.data || typeof evt.data !== 'object') return;
|
||||
const d = evt.data;
|
||||
if (d.type === 'cmms-ai-editor-get-buffer') {
|
||||
let buffer = null;
|
||||
try { buffer = await buildProjectView(); } catch (e) { /* reply null */ }
|
||||
try { evt.source.postMessage({ type: 'cmms-ai-editor-buffer', buffer: buffer }, window.location.origin); } catch (e) {}
|
||||
} else if (d.type === 'cmms-ai-editor-apply' && d.editor === 'pcb' && typeof d.new_source === 'string') {
|
||||
try {
|
||||
await applyProjectPatch(d.new_source);
|
||||
} catch (e) {
|
||||
status('Could not apply the AI proposal: ' + (e.message || e), true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function init() {
|
||||
if (CFG.dark) document.documentElement.setAttribute('data-theme', 'dark');
|
||||
injectControls();
|
||||
|
|
|
|||
Loading…
Reference in a new issue