← Back to Files
Memory.cshtml
@page "/memory"
@model MemoryPageModel
@{
ViewData["Title"] = "Memory - KaiChat";
}
<div class="page-header">
<a href="/" class="back-link">← Back to Chat</a>
<h1>Persistent Memory</h1>
<p class="page-subtitle">Directory-backed memory files loaded into KaiChat context.</p>
</div>
<div class="memory-page">
<div class="memory-toolbar">
<button class="btn" onclick="loadMemory()">Refresh</button>
<label class="memory-instruct-label">
<input type="checkbox" id="includeTrash" onchange="loadMemory()" />
Include trash
</label>
</div>
<div class="memory-update-panel" id="memoryUpdatePanel">
<div class="memory-update-status">
<span class="memory-update-label">Memory update</span>
<span class="memory-update-state" id="memoryUpdateState">Checking...</span>
<span class="memory-update-detail" id="memoryUpdateDetail"></span>
</div>
<button type="button" id="memoryUpdateForceBtn" class="btn btn-memory-update" title="Run memory update now">Run now</button>
<button type="button" id="memoryUpdateResetFuseBtn" class="btn btn-memory-update" title="Reset failure fuse and run now" style="display:none">Reset fuse + run now</button>
</div>
<div class="memory-instruct-form">
<p class="memory-instruct-label">Kai can create, update, and soft-delete memory files through chat. Deletions move files to <code>_trash</code> with a tombstone.</p>
</div>
<button class="btn" onclick="showAddFile()" style="margin-bottom:12px">+ Add Memory File</button>
<div id="addFileForm" style="display:none" class="memory-section">
<div class="memory-section-header">
<h3>New Memory File</h3>
</div>
<div class="memory-edit-form" style="display:flex">
<input type="text" id="newFilePath" class="form-input" placeholder="recent.md" />
<textarea id="newFileContent" class="form-input memory-textarea" placeholder="# Recent Memory ..."></textarea>
<div class="memory-edit-actions">
<button class="btn btn-primary" onclick="saveNewFile()">Save</button>
<button class="btn" onclick="hideAddFile()">Cancel</button>
</div>
</div>
</div>
<div id="memoryContent" class="memory-content">
<div class="conv-loading">Loading memory...</div>
</div>
</div>
<script>
function loadMemory() {
var includeTrash = document.getElementById('includeTrash').checked;
fetch('/api/memory/files?includeTrash=' + encodeURIComponent(includeTrash))
.then(function(r) { return r.json(); })
.then(function(files) {
var container = document.getElementById('memoryContent');
if (!files || files.length === 0) {
container.innerHTML = '<div class="memory-empty">No memory files yet.</div>';
return;
}
container.innerHTML = '';
files.forEach(function(file, idx) {
var section = document.createElement('div');
section.className = 'memory-section';
section.dataset.path = file.path;
var actions = '<button class="btn-action" onclick="editFile(' + idx + ')" title="Edit">E</button>';
if (!file.isTrash) {
actions += '<button class="btn-action" onclick="deleteFile(' + idx + ')" title="Soft delete">X</button>';
}
section.innerHTML =
'<div class="memory-section-header">'
+ '<h3>' + escapeHtml(file.path) + '</h3>'
+ '<div class="memory-section-actions">' + actions + '</div>'
+ '</div>'
+ '<div class="memory-section-body" id="memoryBody' + idx + '">'
+ '<pre class="memory-pre">Loading...</pre>'
+ '</div>'
+ '<div class="memory-edit-form" id="memoryEdit' + idx + '" style="display:none">'
+ '<textarea id="memoryEditInput' + idx + '" class="form-input memory-textarea"></textarea>'
+ '<div class="memory-edit-actions">'
+ '<button class="btn btn-primary" onclick="saveEditFile(' + idx + ')">Save</button>'
+ '<button class="btn" onclick="cancelEditFile(' + idx + ')">Cancel</button>'
+ '</div></div>';
container.appendChild(section);
readFile(file.path, idx);
});
window._memoryFiles = files;
});
}
function readFile(path, idx) {
fetch('/api/memory/file/read?path=' + encodeURIComponent(path))
.then(function(r) { return r.json(); })
.then(function(data) {
var content = data.content || '';
document.querySelector('#memoryBody' + idx + ' .memory-pre').textContent = content;
document.getElementById('memoryEditInput' + idx).value = content;
});
}
function editFile(idx) {
document.getElementById('memoryBody' + idx).style.display = 'none';
document.getElementById('memoryEdit' + idx).style.display = 'block';
}
function cancelEditFile(idx) {
document.getElementById('memoryBody' + idx).style.display = 'block';
document.getElementById('memoryEdit' + idx).style.display = 'none';
}
function saveEditFile(idx) {
var file = window._memoryFiles[idx];
var content = document.getElementById('memoryEditInput' + idx).value;
saveFile(file.path, content).then(loadMemory);
}
function deleteFile(idx) {
var file = window._memoryFiles[idx];
var reason = prompt('Reason for soft-deleting ' + file.path + '?');
if (!reason || !reason.trim()) return;
fetch('/api/memory/file/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: file.path, reason: reason.trim() })
}).then(function() { loadMemory(); });
}
function showAddFile() {
document.getElementById('addFileForm').style.display = 'block';
}
function hideAddFile() {
document.getElementById('addFileForm').style.display = 'none';
}
function saveNewFile() {
var path = document.getElementById('newFilePath').value.trim();
var content = document.getElementById('newFileContent').value;
if (!path || !content.trim()) return;
saveFile(path, content).then(function() {
document.getElementById('newFilePath').value = '';
document.getElementById('newFileContent').value = '';
hideAddFile();
loadMemory();
});
}
function saveFile(path, content) {
return fetch('/api/memory/file/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: path, content: content })
}).then(function(r) { return r.json(); });
}
function escapeHtml(text) {
var d = document.createElement('div');
d.textContent = text;
return d.innerHTML;
}
document.addEventListener('DOMContentLoaded', loadMemory);
</script>