mirror of
https://github.com/bal-spec/sillytavern-character-memory.git
synced 2026-04-26 10:50:55 +00:00
Complete rewrite of the UI and significant feature additions since v1.6.1. UX Redesign (v2.0): - Single-view dashboard replaces 4-tab sidebar - Settings, Prompts, Troubleshooter, Memory Manager moved to center-screen modals - Activity log in slide-out drawer - Setup Wizard for first-run configuration - Prompt version tracking with update notifications - Health indicator in stats bar Injection Viewer (v1.6–v2.1.6): - Per-message injection data: see exactly what memories, lorebook entries, and extension prompts were injected for any generation - Context/Prompt Breakdown with per-category token counts (System, Char card, Lorebook, Data Bank, Examples, Chat history) via ST Prompt Itemization - Stacked bar visualization, token hints in headers, Tips popup - Context overflow and heavy injection warnings Memory Management: - Unified block editor across all 5 editing surfaces (Memory Manager, Consolidation, Conversion, Reformat, Data Bank browser) - Find & Replace with highlighting across all editors - Undo support for all edit operations - Group chat character picker in Memory Manager Other features: - Tablet & phone display modes with touch-friendly controls - Topic-tagged memory format for better vector retrieval - Self-closing memory tag handling (GLM-4.7 compatibility) - Protect recent messages from extraction feedback loop - 9-point health check system with retrieve chunks and score threshold - Shared editor factory (editor.js), pure utility library (lib.js) - Vitest test suite: unit, snapshot, and live LLM tests - Full documentation suite in docs/ See CHANGELOG.md for detailed per-version notes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
110 lines
3.3 KiB
JavaScript
110 lines
3.3 KiB
JavaScript
import { cloneMemoryBlocks, countMatchesInBlocks, getTimestamp, reindexEditingSet, replaceInBlocks } from './lib.js';
|
|
|
|
/**
|
|
* Create a memory block editor with state management and undo.
|
|
* Pure state logic — no DOM. Callers handle rendering and event binding.
|
|
*
|
|
* @param {object} options
|
|
* @param {Array<{chat: string, date: string, bullets: string[]}>} options.blocks - Initial blocks
|
|
* @returns {object} Editor API
|
|
*/
|
|
export function createMemoryEditor({ blocks }) {
|
|
let editorBlocks = cloneMemoryBlocks(blocks);
|
|
const versionStack = [];
|
|
const editingSet = new Set();
|
|
|
|
function saveVersion() {
|
|
versionStack.push(cloneMemoryBlocks(editorBlocks));
|
|
}
|
|
|
|
return {
|
|
getBlocks() {
|
|
return cloneMemoryBlocks(editorBlocks);
|
|
},
|
|
|
|
deleteBullet(blockIndex, bulletIndex) {
|
|
if (!editorBlocks[blockIndex]) return;
|
|
saveVersion();
|
|
editorBlocks[blockIndex].bullets.splice(bulletIndex, 1);
|
|
if (editorBlocks[blockIndex].bullets.length === 0) {
|
|
editorBlocks.splice(blockIndex, 1);
|
|
reindexEditingSet(editingSet, blockIndex);
|
|
}
|
|
},
|
|
|
|
deleteBlock(blockIndex) {
|
|
if (!editorBlocks[blockIndex]) return;
|
|
saveVersion();
|
|
editorBlocks.splice(blockIndex, 1);
|
|
reindexEditingSet(editingSet, blockIndex);
|
|
},
|
|
|
|
addBullet(blockIndex) {
|
|
if (!editorBlocks[blockIndex]) return;
|
|
saveVersion();
|
|
editorBlocks[blockIndex].bullets.push('');
|
|
editingSet.add(blockIndex);
|
|
},
|
|
|
|
addBlock(timestamp) {
|
|
saveVersion();
|
|
editorBlocks.push({
|
|
chat: 'New Group',
|
|
date: timestamp || getTimestamp(),
|
|
bullets: [''],
|
|
});
|
|
editingSet.add(editorBlocks.length - 1);
|
|
},
|
|
|
|
updateBullet(blockIndex, bulletIndex, text) {
|
|
if (!editorBlocks[blockIndex]) return;
|
|
editorBlocks[blockIndex].bullets[bulletIndex] = text;
|
|
},
|
|
|
|
updateTheme(blockIndex, label) {
|
|
if (!editorBlocks[blockIndex]) return;
|
|
editorBlocks[blockIndex].chat = label;
|
|
},
|
|
|
|
undo() {
|
|
if (versionStack.length === 0) return false;
|
|
editorBlocks = versionStack.pop();
|
|
return true;
|
|
},
|
|
|
|
canUndo() {
|
|
return versionStack.length > 0;
|
|
},
|
|
|
|
replaceAll(newBlocks) {
|
|
editorBlocks = cloneMemoryBlocks(newBlocks);
|
|
versionStack.length = 0;
|
|
editingSet.clear();
|
|
},
|
|
|
|
toggleEdit(blockIndex) {
|
|
if (editingSet.has(blockIndex)) {
|
|
editingSet.delete(blockIndex);
|
|
} else {
|
|
editingSet.add(blockIndex);
|
|
}
|
|
},
|
|
|
|
isEditing(blockIndex) {
|
|
return editingSet.has(blockIndex);
|
|
},
|
|
|
|
getEditingSet() {
|
|
return new Set(editingSet);
|
|
},
|
|
|
|
countMatches(find, caseSensitive = false) {
|
|
return countMatchesInBlocks(editorBlocks, find, caseSensitive);
|
|
},
|
|
|
|
findAndReplaceAll(find, replace, caseSensitive = false) {
|
|
saveVersion();
|
|
return { replacements: replaceInBlocks(editorBlocks, find, replace, caseSensitive) };
|
|
},
|
|
};
|
|
}
|