rowm-polymorphic-notebook / frontend /scripts /notebook-context.js
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/rowm-polymorphic-notebook
1d3f990 verified
Raw
History Blame Contribute Delete
8.32 kB
/**
* Notebook Context Builder
* Extracts notebook state for WebLLM and Tau Prolog
*/
class NotebookContextBuilder {
constructor() {
this.maxContextTokens = 4000; // Conservative limit
this.maxCellSummary = 500;
}
/**
* Extract notebook cells from DOM
*/
extractCells() {
const cellElements = document.querySelectorAll('.notebook-cell');
const cells = [];
cellElements.forEach((elem, index) => {
const source = elem.querySelector('.cell-editor textarea')?.value || '';
const output = elem.querySelector('.cell-output')?.textContent || '';
const cellType = elem.dataset.cellType || 'code';
cells.push({
index,
type: cellType,
source: source.substring(0, this.maxCellSummary),
output: output.substring(0, this.maxCellSummary),
hash: this.hashCell(source, output),
});
});
return cells;
}
/**
* Simple hash for cell identification
*/
hashCell(source, output) {
const combined = source + output;
let hash = 0;
for (let i = 0; i < combined.length; i++) {
const char = combined.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash).toString(16);
}
/**
* Build notebook summary
*/
buildNotebookSummary() {
const notebookTitle = document.querySelector('#notebook-header h1')?.textContent || 'Untitled';
const cells = this.extractCells();
const summary = {
title: notebookTitle,
cellCount: cells.length,
cells: cells,
recentCells: cells.slice(-3), // Last 3 cells
};
return summary;
}
/**
* Build context packet for LLM
*/
buildContextPacket(userQuestion, selectedCellIndex = null) {
const summary = this.buildNotebookSummary();
let tokenCount = 0;
const packet = {
question: userQuestion,
notebookTitle: summary.title,
cells: [],
recentOutputs: [],
trustPolicies: this.extractTrustPolicies(),
receiptSummary: this.extractReceiptMetadata(),
};
// Add nearby cells
for (const cell of summary.cells) {
if (selectedCellIndex !== null && Math.abs(cell.index - selectedCellIndex) > 5) {
continue; // Skip distant cells
}
const cellText = `Cell ${cell.index} (${cell.type}):\n${cell.source}\nOutput: ${cell.output}\n`;
tokenCount += cellText.length / 4; // Rough token estimate
if (tokenCount < this.maxContextTokens) {
packet.cells.push({
index: cell.index,
type: cell.type,
source: cell.source,
output: cell.output,
});
}
}
// Add recent outputs for context
for (const cell of summary.recentCells) {
if (cell.output) {
packet.recentOutputs.push({
cellIndex: cell.index,
output: cell.output.substring(0, 200),
});
}
}
return packet;
}
/**
* Extract trust policies from sidebar
*/
extractTrustPolicies() {
const trustElements = document.querySelectorAll('#trust-list div');
const policies = [];
trustElements.forEach(elem => {
const text = elem.textContent.trim();
if (text) {
policies.push(text);
}
});
return policies;
}
/**
* Extract WORM receipt metadata
*/
extractReceiptMetadata() {
const receiptElements = document.querySelectorAll('.cell-receipt');
const metadata = {
totalReceipts: receiptElements.length,
recentReceipts: [],
};
// Get last 3 receipt hashes
Array.from(receiptElements)
.slice(-3)
.forEach(elem => {
const hash = elem.dataset.hash || 'unknown';
const status = elem.dataset.status || 'sealed';
metadata.recentReceipts.push({ hash: hash.substring(0, 16), status });
});
return metadata;
}
/**
* Format context for system prompt
*/
formatSystemPrompt(packet) {
let prompt = `You are the assistant for the Isomorphic WORM Notebook.\n\n`;
prompt += `Notebook: "${packet.notebookTitle}"\n`;
prompt += `Total cells: ${packet.cells.length}\n`;
prompt += `Recent receipts: ${packet.receiptSummary.totalReceipts}\n\n`;
if (packet.cells.length > 0) {
prompt += `Visible cells:\n`;
for (const cell of packet.cells) {
prompt += `\n[Cell ${cell.index}] ${cell.type}\n`;
prompt += `Source: ${cell.source.substring(0, 100)}...\n`;
if (cell.output) {
prompt += `Output: ${cell.output.substring(0, 100)}...\n`;
}
}
}
if (packet.trustPolicies.length > 0) {
prompt += `\nTrust policies:\n`;
for (const policy of packet.trustPolicies) {
prompt += `- ${policy}\n`;
}
}
prompt += `\nUser question: ${packet.question}\n`;
prompt += `Please answer concisely, cite cell references (Cell N), and preserve Unicode exactly.`;
return prompt;
}
/**
* Estimate token count
*/
estimateTokens(text) {
// Rough estimate: ~1 token per 4 characters
return Math.ceil(text.length / 4);
}
/**
* Validate context packet size
*/
validateContextSize(packet) {
const systemPrompt = this.formatSystemPrompt(packet);
const tokenCount = this.estimateTokens(systemPrompt);
return {
valid: tokenCount < this.maxContextTokens,
tokenCount: tokenCount,
maxTokens: this.maxContextTokens,
warning: tokenCount > this.maxContextTokens * 0.8 ? 'Context approaching limit' : null,
};
}
/**
* Extract cell by index
*/
getCellByIndex(index) {
const cells = this.extractCells();
return cells.find(c => c.index === index);
}
/**
* Get dependency graph (cells that reference each other)
*/
buildDependencyGraph() {
const cells = this.extractCells();
const graph = {};
cells.forEach(cell => {
graph[cell.index] = [];
// Simple heuristic: look for cell references in source
const cellReferences = cell.source.match(/Cell\s*(\d+)/gi) || [];
for (const ref of cellReferences) {
const refIndex = parseInt(ref.match(/\d+/)[0]);
if (refIndex !== cell.index && cells.some(c => c.index === refIndex)) {
graph[cell.index].push(refIndex);
}
}
});
return graph;
}
/**
* Export context as JSON (for debugging)
*/
exportContext(userQuestion, selectedCellIndex = null) {
const packet = this.buildContextPacket(userQuestion, selectedCellIndex);
const systemPrompt = this.formatSystemPrompt(packet);
const validation = this.validateContextSize(packet);
return {
timestamp: new Date().toISOString(),
notebook: packet.notebookTitle,
question: userQuestion,
systemPrompt: systemPrompt,
contextPacket: packet,
validation: validation,
dependencyGraph: this.buildDependencyGraph(),
};
}
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = NotebookContextBuilder;
}