Compare commits
1 Commits
mimo/code/
...
mimo/build
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fde7569e4 |
84
app.js
84
app.js
@@ -48,6 +48,9 @@ let chatOpen = true;
|
||||
let loadProgress = 0;
|
||||
let performanceTier = 'high';
|
||||
|
||||
// ═══ UI MODE (VISITOR / OPERATOR) ═══
|
||||
let uiMode = 'visitor'; // 'visitor' | 'operator'
|
||||
|
||||
// ═══ HERMES WS STATE ═══
|
||||
let hermesWs = null;
|
||||
let wsReconnectTimer = null;
|
||||
@@ -1867,6 +1870,9 @@ function setupControls() {
|
||||
if (e.key.toLowerCase() === 'v' && document.activeElement !== document.getElementById('chat-input')) {
|
||||
cycleNavMode();
|
||||
}
|
||||
if (e.key.toLowerCase() === 'o' && document.activeElement !== document.getElementById('chat-input')) {
|
||||
toggleUIMode();
|
||||
}
|
||||
if (e.key.toLowerCase() === 'f' && activePortal && !portalOverlayActive) {
|
||||
activatePortal(activePortal);
|
||||
}
|
||||
@@ -1964,18 +1970,9 @@ function setupControls() {
|
||||
});
|
||||
document.getElementById('chat-send').addEventListener('click', () => sendChatMessage());
|
||||
|
||||
// Add MemPalace mining button
|
||||
// Add MemPalace mining button (operator-only)
|
||||
document.querySelector('.chat-quick-actions').innerHTML += `
|
||||
<button class="quick-action-btn" onclick="mineMemPalaceContent()">Mine Chat</button>
|
||||
<div id="mem-palace-stats" class="mem-palace-stats">
|
||||
<div>Compression: <span id="compression-ratio">--</span>x</div>
|
||||
<div>Docs: <span id="docs-mined">0</span></div>
|
||||
<div>AAAK: <span id="aaak-size">0B</span></div>
|
||||
<div>Compression: <span id="compression-ratio">--</span>x</div>
|
||||
<div>Docs: <span id="docs-mined">0</span></div>
|
||||
<div>AAAK: <span id="aaak-size">0B</span></div>
|
||||
<div class="mem-palace-logs" style="margin-top:4px; font-size:10px; color:#4af0c0;">Logs: <span id="mem-logs">0</span></div>
|
||||
</div>
|
||||
<button class="quick-action-btn operator-only" onclick="mineMemPalaceContent()">Mine Chat</button>
|
||||
`;
|
||||
|
||||
// Chat quick actions
|
||||
@@ -2007,29 +2004,52 @@ function setupControls() {
|
||||
document.getElementById('atlas-toggle-btn').addEventListener('click', openPortalAtlas);
|
||||
document.getElementById('atlas-close-btn').addEventListener('click', closePortalAtlas);
|
||||
|
||||
// Mnemosyne export/import (#1174)
|
||||
document.getElementById('mnemosyne-export-btn').addEventListener('click', () => {
|
||||
const result = SpatialMemory.exportToFile();
|
||||
if (result) {
|
||||
addChatMessage('system', 'Mnemosyne: Exported ' + result.count + ' memories to ' + result.filename);
|
||||
}
|
||||
});
|
||||
// ═══ VISITOR / OPERATOR MODE TOGGLE ═══
|
||||
// Restore saved mode from localStorage
|
||||
const savedMode = localStorage.getItem('nexus-ui-mode');
|
||||
if (savedMode === 'operator') {
|
||||
uiMode = 'operator';
|
||||
}
|
||||
applyUIMode();
|
||||
|
||||
document.getElementById('mnemosyne-import-btn').addEventListener('click', () => {
|
||||
document.getElementById('mnemosyne-import-file').click();
|
||||
});
|
||||
// Create and append mode toggle button to the HUD controls area
|
||||
const modeBtn = document.createElement('button');
|
||||
modeBtn.id = 'mode-toggle-btn';
|
||||
modeBtn.className = 'mode-toggle-btn';
|
||||
modeBtn.setAttribute('data-mode', uiMode);
|
||||
modeBtn.innerHTML = modeToggleLabel();
|
||||
modeBtn.title = uiMode === 'visitor' ? 'Switch to Operator mode' : 'Switch to Visitor mode';
|
||||
modeBtn.addEventListener('click', toggleUIMode);
|
||||
|
||||
document.getElementById('mnemosyne-import-file').addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
const result = await SpatialMemory.importFromFile(file);
|
||||
addChatMessage('system', 'Mnemosyne: Imported ' + result.count + ' of ' + result.total + ' memories');
|
||||
} catch (err) {
|
||||
addChatMessage('system', 'Mnemosyne: Import failed — ' + err.message);
|
||||
}
|
||||
e.target.value = '';
|
||||
});
|
||||
const hudEl = document.getElementById('hud');
|
||||
if (hudEl) hudEl.appendChild(modeBtn);
|
||||
}
|
||||
|
||||
function toggleUIMode() {
|
||||
uiMode = uiMode === 'visitor' ? 'operator' : 'visitor';
|
||||
localStorage.setItem('nexus-ui-mode', uiMode);
|
||||
applyUIMode();
|
||||
|
||||
// Update button
|
||||
const btn = document.getElementById('mode-toggle-btn');
|
||||
if (btn) {
|
||||
btn.setAttribute('data-mode', uiMode);
|
||||
btn.innerHTML = modeToggleLabel();
|
||||
btn.title = uiMode === 'visitor' ? 'Switch to Operator mode' : 'Switch to Visitor mode';
|
||||
}
|
||||
|
||||
addChatMessage('system', `Mode: ${uiMode.toUpperCase()}. ${uiMode === 'operator' ? 'Operator panels enabled.' : 'Visitor view. Clean and focused.'}`);
|
||||
}
|
||||
|
||||
function modeToggleLabel() {
|
||||
if (uiMode === 'visitor') {
|
||||
return '<span class="mode-icon">👁</span> VISITOR';
|
||||
}
|
||||
return '<span class="mode-icon">⚙</span> OPERATOR';
|
||||
}
|
||||
|
||||
function applyUIMode() {
|
||||
document.body.classList.toggle('visitor-mode', uiMode === 'visitor');
|
||||
}
|
||||
|
||||
function sendChatMessage(overrideText = null) {
|
||||
|
||||
@@ -157,7 +157,8 @@
|
||||
<!-- Controls hint + nav mode -->
|
||||
<div class="hud-controls">
|
||||
<span>WASD</span> move <span>Mouse</span> look <span>Enter</span> chat
|
||||
<span>V</span> mode: <span id="nav-mode-label">WALK</span>
|
||||
<span>V</span> nav: <span id="nav-mode-label">WALK</span>
|
||||
<span>O</span> toggle mode
|
||||
<span id="nav-mode-hint" class="nav-mode-hint"></span>
|
||||
<span class="ws-hud-status">HERMES: <span id="ws-status-dot" class="chat-status-dot"></span></span>
|
||||
</div>
|
||||
@@ -233,11 +234,6 @@
|
||||
<div class="memory-meta-row"><span class="memory-meta-label">Time</span><span id="memory-panel-time">\u2014</span></div>
|
||||
<div class="memory-meta-row memory-meta-row--related"><span class="memory-meta-label">Related</span><span id="memory-panel-connections">\u2014</span></div>
|
||||
</div>
|
||||
<div class="memory-panel-actions">
|
||||
<button id="mnemosyne-export-btn" class="mnemosyne-action-btn" title="Export spatial memory to JSON">⤓ Export</button>
|
||||
<button id="mnemosyne-import-btn" class="mnemosyne-action-btn" title="Import spatial memory from JSON">⤒ Import</button>
|
||||
<input type="file" id="mnemosyne-import-file" accept=".json" style="display:none;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -652,93 +652,11 @@ const SpatialMemory = (() => {
|
||||
return _selectedId;
|
||||
}
|
||||
|
||||
// ─── FILE EXPORT ──────────────────────────────────────
|
||||
function exportToFile() {
|
||||
const index = exportIndex();
|
||||
const json = JSON.stringify(index, null, 2);
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
const filename = 'mnemosyne-export-' + date + '.json';
|
||||
|
||||
const blob = new Blob([json], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
console.info('[Mnemosyne] Exported', index.memories.length, 'memories to', filename);
|
||||
return { filename, count: index.memories.length };
|
||||
}
|
||||
|
||||
// ─── FILE IMPORT ──────────────────────────────────────
|
||||
function importFromFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!file) {
|
||||
reject(new Error('No file provided'));
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
const data = JSON.parse(e.target.result);
|
||||
|
||||
// Schema validation
|
||||
if (!data || typeof data !== 'object') {
|
||||
reject(new Error('Invalid JSON: not an object'));
|
||||
return;
|
||||
}
|
||||
if (typeof data.version !== 'number') {
|
||||
reject(new Error('Invalid schema: missing version field'));
|
||||
return;
|
||||
}
|
||||
if (data.version !== STORAGE_VERSION) {
|
||||
reject(new Error('Version mismatch: got ' + data.version + ', expected ' + STORAGE_VERSION));
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(data.memories)) {
|
||||
reject(new Error('Invalid schema: memories is not an array'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate each memory entry
|
||||
for (let i = 0; i < data.memories.length; i++) {
|
||||
const mem = data.memories[i];
|
||||
if (!mem.id || typeof mem.id !== 'string') {
|
||||
reject(new Error('Invalid memory at index ' + i + ': missing or invalid id'));
|
||||
return;
|
||||
}
|
||||
if (!mem.category || typeof mem.category !== 'string') {
|
||||
reject(new Error('Invalid memory "' + mem.id + '": missing category'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const count = importIndex(data);
|
||||
saveToStorage();
|
||||
console.info('[Mnemosyne] Imported', count, 'memories from file');
|
||||
resolve({ count, total: data.memories.length });
|
||||
} catch (parseErr) {
|
||||
reject(new Error('Failed to parse JSON: ' + parseErr.message));
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function() {
|
||||
reject(new Error('Failed to read file'));
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init, placeMemory, removeMemory, update,
|
||||
getMemoryAtPosition, getRegionAtPosition, getMemoriesInRegion, getAllMemories,
|
||||
getCrystalMeshes, getMemoryFromMesh, highlightMemory, clearHighlight, getSelectedId,
|
||||
exportIndex, importIndex, exportToFile, importFromFile, searchNearby, REGIONS,
|
||||
exportIndex, importIndex, searchNearby, REGIONS,
|
||||
saveToStorage, loadFromStorage, clearStorage,
|
||||
runGravityLayout
|
||||
};
|
||||
|
||||
118
style.css
118
style.css
@@ -1461,43 +1461,6 @@ canvas#nexus-canvas {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════
|
||||
PROJECT MNEMOSYNE — EXPORT/IMPORT ACTIONS (#1174)
|
||||
═══════════════════════════════════════════════════════ */
|
||||
|
||||
.memory-panel-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid rgba(123, 92, 255, 0.15);
|
||||
}
|
||||
|
||||
.mnemosyne-action-btn {
|
||||
flex: 1;
|
||||
padding: 6px 10px;
|
||||
background: rgba(123, 92, 255, 0.12);
|
||||
border: 1px solid rgba(123, 92, 255, 0.3);
|
||||
border-radius: 6px;
|
||||
color: #a08cff;
|
||||
font-size: 11px;
|
||||
font-family: monospace;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mnemosyne-action-btn:hover {
|
||||
background: rgba(123, 92, 255, 0.25);
|
||||
border-color: rgba(123, 92, 255, 0.6);
|
||||
color: #c4b5ff;
|
||||
}
|
||||
|
||||
.mnemosyne-action-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════
|
||||
PROJECT MNEMOSYNE — SESSION ROOM HUD PANEL (#1171)
|
||||
═══════════════════════════════════════════════════════ */
|
||||
@@ -1617,3 +1580,84 @@ canvas#nexus-canvas {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* === VISITOR / OPERATOR MODE TOGGLE === */
|
||||
.mode-toggle-btn {
|
||||
position: absolute;
|
||||
bottom: var(--space-3);
|
||||
right: var(--space-3);
|
||||
pointer-events: auto;
|
||||
background: rgba(10, 15, 40, 0.7);
|
||||
border: 1px solid var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
padding: 6px 14px;
|
||||
font-family: var(--font-display);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
backdrop-filter: blur(5px);
|
||||
transition: all var(--transition-ui);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.mode-toggle-btn:hover {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-bg);
|
||||
box-shadow: 0 0 15px var(--color-primary);
|
||||
}
|
||||
|
||||
.mode-toggle-btn .mode-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mode-toggle-btn[data-mode="operator"] {
|
||||
border-color: var(--color-gold);
|
||||
color: var(--color-gold);
|
||||
box-shadow: 0 0 8px rgba(255, 215, 0, 0.15);
|
||||
}
|
||||
|
||||
.mode-toggle-btn[data-mode="operator"]:hover {
|
||||
background: var(--color-gold);
|
||||
color: var(--color-bg);
|
||||
box-shadow: 0 0 15px var(--color-gold);
|
||||
}
|
||||
|
||||
/* Visitor mode: hide operator-only surfaces */
|
||||
body.visitor-mode .gofai-hud,
|
||||
body.visitor-mode .hud-agent-log,
|
||||
body.visitor-mode .hud-debug,
|
||||
body.visitor-mode .bannerlord-hud,
|
||||
body.visitor-mode #mem-palace-status,
|
||||
body.visitor-mode #mem-palace-controls,
|
||||
body.visitor-mode #mempalace-results,
|
||||
body.visitor-mode .mem-palace-ui,
|
||||
body.visitor-mode .mem-palace-stats,
|
||||
body.visitor-mode .ws-hud-status,
|
||||
body.visitor-mode .chat-quick-actions .operator-only,
|
||||
body.visitor-mode .nexus-footer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Visitor mode: simplify controls hint */
|
||||
body.visitor-mode .hud-controls {
|
||||
font-size: var(--text-xs);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Visitor mode: cleaner chat */
|
||||
body.visitor-mode .chat-panel {
|
||||
width: 320px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
/* Operator badge in operator mode */
|
||||
body:not(.visitor-mode) .hud-controls::after {
|
||||
content: ' OPERATOR';
|
||||
color: var(--color-gold);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user