| layout | default |
|---|---|
| title | Chapter 3: Chat Management |
| parent | SillyTavern Tutorial |
| nav_order | 3 |
Welcome to Chapter 3: Chat Management. In this part of SillyTavern Tutorial: Advanced LLM Frontend for Power Users, you will build an intuitive mental model first, then move into concrete implementation details and practical production tradeoffs.
Master conversation organization, branching, and history management for complex narratives.
Effective chat management is essential for maintaining long-running conversations, exploring alternative storylines, and organizing your creative projects. SillyTavern provides powerful tools for managing your chat history.
┌─────────────────────────────────────────────────────────────────┐
│ Chat Organization Structure │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Character: Luna Starweaver │
│ └── Chats │
│ ├── Main Story (Active) │
│ │ ├── Message 1 - 500 │
│ │ └── Branches │
│ │ ├── Branch: "What if Luna was angry?" │
│ │ └── Branch: "Alternative meeting" │
│ ├── Side Quest: The Lost Spell │
│ │ └── Message 1 - 150 │
│ └── Practice Session │
│ └── Message 1 - 50 │
│ │
│ Character: Dr. Sarah Chen │
│ └── Chats │
│ ├── Episode 1: First Contact │
│ ├── Episode 2: The Discovery │
│ └── Research Notes (OOC) │
│ │
└─────────────────────────────────────────────────────────────────┘
// Chat metadata structure
const chatMetadata = {
chatId: "luna_main_001",
characterId: "luna_starweaver",
name: "Main Story - Crystal Academy",
createdAt: "2024-01-15T10:30:00Z",
lastMessage: "2024-02-20T15:45:00Z",
messageCount: 523,
// Organization
tags: ["main_story", "ongoing", "fantasy"],
folder: "Luna Campaigns",
pinned: true,
// State tracking
currentBranch: "main",
branches: ["main", "angry_luna", "alternative_meeting"],
bookmarks: [
{ messageId: 45, label: "First spell learned" },
{ messageId: 156, label: "Confrontation with dark mage" },
{ messageId: 389, label: "Graduation ceremony" }
]
};
// Chat operations
const chatOperations = {
// Create new chat
createChat(characterId, name, options = {}) {
return {
id: generateId(),
characterId,
name,
messages: [],
...options
};
},
// Rename chat
renameChat(chatId, newName) {
const chat = getChat(chatId);
chat.name = newName;
chat.modifiedAt = new Date().toISOString();
saveChat(chat);
},
// Delete chat
deleteChat(chatId, permanent = false) {
if (permanent) {
removeFromStorage(chatId);
} else {
moveToTrash(chatId);
}
}
};// Branch from specific message
function createBranch(chatId, messageIndex, branchName) {
const chat = getChat(chatId);
// Copy messages up to branch point
const branchMessages = chat.messages.slice(0, messageIndex + 1);
const branch = {
id: generateId(),
parentChatId: chatId,
branchPoint: messageIndex,
name: branchName,
messages: [...branchMessages],
createdAt: new Date().toISOString()
};
chat.branches.push(branch);
saveChat(chat);
return branch;
}
// Example usage
const angryBranch = createBranch(
"luna_main_001",
156, // Branch after message 156
"What if Luna got angry?"
);┌─────────────────────────────────────────────────────────────────┐
│ Conversation Branch Tree │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Message 1 ──▶ Message 2 ──▶ ... ──▶ Message 156 │
│ │ │
│ ├──▶ Main Branch │
│ │ Message 157 │
│ │ Message 158 │
│ │ ... │
│ │ Message 523 │
│ │ │
│ └──▶ "Angry Luna" │
│ Message 157' │
│ (Luna gets upset)│
│ Message 158' │
│ ... │
│ Message 245' │
│ │
└─────────────────────────────────────────────────────────────────┘
// Merge branch back to main
function mergeBranch(branchId, targetChatId, mergeStrategy = 'append') {
const branch = getBranch(branchId);
const targetChat = getChat(targetChatId);
switch (mergeStrategy) {
case 'append':
// Add branch messages after current
targetChat.messages.push(...branch.messages.slice(branch.branchPoint + 1));
break;
case 'replace':
// Replace from branch point
targetChat.messages = [
...targetChat.messages.slice(0, branch.branchPoint + 1),
...branch.messages.slice(branch.branchPoint + 1)
];
break;
case 'interleave':
// Create new branch showing both paths
// Used for "parallel universe" storytelling
break;
}
saveChat(targetChat);
}// Edit existing message
function editMessage(chatId, messageIndex, newContent) {
const chat = getChat(chatId);
const message = chat.messages[messageIndex];
// Store edit history
if (!message.editHistory) {
message.editHistory = [];
}
message.editHistory.push({
content: message.content,
editedAt: new Date().toISOString()
});
message.content = newContent;
message.lastEdited = new Date().toISOString();
saveChat(chat);
}
// Regenerate AI response
async function regenerateResponse(chatId, messageIndex) {
const chat = getChat(chatId);
const aiMessage = chat.messages[messageIndex];
if (aiMessage.role !== 'assistant') {
throw new Error('Can only regenerate AI messages');
}
// Store previous version
if (!aiMessage.alternatives) {
aiMessage.alternatives = [];
}
aiMessage.alternatives.push(aiMessage.content);
// Get context up to this point
const context = chat.messages.slice(0, messageIndex);
// Generate new response
const newResponse = await generateResponse(context);
aiMessage.content = newResponse;
aiMessage.regeneratedAt = new Date().toISOString();
saveChat(chat);
return newResponse;
}// Navigate between alternative responses
const swipeManager = {
current: 0,
alternatives: [],
// Initialize swipe for a message
initSwipe(message) {
this.alternatives = [message.content, ...(message.alternatives || [])];
this.current = 0;
},
// Swipe left (previous)
swipeLeft() {
if (this.current > 0) {
this.current--;
return this.alternatives[this.current];
}
return null;
},
// Swipe right (next)
swipeRight() {
if (this.current < this.alternatives.length - 1) {
this.current++;
return this.alternatives[this.current];
}
return null;
},
// Generate new alternative
async generateNew(context) {
const newResponse = await generateResponse(context);
this.alternatives.push(newResponse);
this.current = this.alternatives.length - 1;
return newResponse;
}
};// Bookmark important messages
function createBookmark(chatId, messageIndex, label, color = 'blue') {
const chat = getChat(chatId);
const bookmark = {
id: generateId(),
messageIndex,
label,
color,
createdAt: new Date().toISOString(),
notes: ''
};
chat.bookmarks.push(bookmark);
saveChat(chat);
return bookmark;
}
// Bookmark categories
const bookmarkColors = {
blue: 'Important moment',
green: 'Character development',
yellow: 'Plot point',
red: 'Conflict/tension',
purple: 'World building',
orange: 'Reference/research'
};// Jump to specific points in chat
const navigationTools = {
// Jump to bookmark
goToBookmark(chatId, bookmarkId) {
const chat = getChat(chatId);
const bookmark = chat.bookmarks.find(b => b.id === bookmarkId);
scrollToMessage(bookmark.messageIndex);
},
// Search in chat
searchChat(chatId, query, options = {}) {
const chat = getChat(chatId);
const results = [];
chat.messages.forEach((message, index) => {
if (message.content.toLowerCase().includes(query.toLowerCase())) {
results.push({
index,
message,
context: getMessageContext(chat, index)
});
}
});
return results;
},
// Filter by role
filterByRole(chatId, role) {
const chat = getChat(chatId);
return chat.messages
.map((m, i) => ({ ...m, index: i }))
.filter(m => m.role === role);
}
};// Export chat in various formats
const exportFormats = {
// JSON export (full data)
exportJSON(chat) {
return JSON.stringify(chat, null, 2);
},
// Plain text export
exportText(chat) {
return chat.messages.map(m => {
const role = m.role === 'user' ? 'You' : chat.characterName;
return `${role}: ${m.content}`;
}).join('\n\n');
},
// Markdown export
exportMarkdown(chat) {
let md = `# ${chat.name}\n\n`;
md += `**Character:** ${chat.characterName}\n`;
md += `**Date:** ${chat.createdAt}\n\n---\n\n`;
chat.messages.forEach(m => {
const role = m.role === 'user' ? '**You**' : `**${chat.characterName}**`;
md += `${role}:\n\n${m.content}\n\n---\n\n`;
});
return md;
},
// HTML export
exportHTML(chat) {
let html = `<!DOCTYPE html>
<html>
<head><title>${chat.name}</title></head>
<body>
<h1>${chat.name}</h1>
<div class="chat-log">`;
chat.messages.forEach(m => {
const roleClass = m.role === 'user' ? 'user-message' : 'ai-message';
html += `<div class="${roleClass}">
<strong>${m.role === 'user' ? 'You' : chat.characterName}</strong>
<p>${escapeHtml(m.content)}</p>
</div>`;
});
html += '</div></body></html>';
return html;
}
};// Backup configuration
const backupConfig = {
enabled: true,
frequency: 'hourly', // hourly, daily, weekly
maxBackups: 10,
location: './backups',
includeMedia: true
};
// Backup system
const backupSystem = {
// Create backup
async createBackup() {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = `${backupConfig.location}/backup_${timestamp}`;
// Get all chats
const chats = await getAllChats();
// Create backup structure
const backup = {
version: '1.0',
createdAt: new Date().toISOString(),
chats,
settings: await getSettings(),
characters: await getAllCharacters()
};
// Save backup
await saveFile(backupPath + '.json', JSON.stringify(backup));
// Clean old backups
await this.cleanOldBackups();
return backupPath;
},
// Restore from backup
async restoreBackup(backupPath, options = {}) {
const backup = await loadFile(backupPath);
const data = JSON.parse(backup);
if (options.chats !== false) {
await restoreChats(data.chats);
}
if (options.characters !== false) {
await restoreCharacters(data.characters);
}
if (options.settings !== false) {
await restoreSettings(data.settings);
}
return { success: true, restored: data };
},
// Clean old backups
async cleanOldBackups() {
const backups = await getBackupList();
if (backups.length > backupConfig.maxBackups) {
const toDelete = backups
.sort((a, b) => a.date - b.date)
.slice(0, backups.length - backupConfig.maxBackups);
for (const backup of toDelete) {
await deleteFile(backup.path);
}
}
}
};// Manage what gets sent to the AI
const contextManager = {
maxTokens: 4096,
buildContext(chat, options = {}) {
const context = [];
let tokenCount = 0;
// System prompt (always included)
const systemPrompt = buildSystemPrompt(chat.character);
context.push({ role: 'system', content: systemPrompt });
tokenCount += estimateTokens(systemPrompt);
// World book entries (if triggered)
const worldEntries = getTriggeredWorldEntries(chat);
worldEntries.forEach(entry => {
context.push({ role: 'system', content: entry });
tokenCount += estimateTokens(entry);
});
// Message history (as many as fit)
const messages = [...chat.messages].reverse();
for (const message of messages) {
const tokens = estimateTokens(message.content);
if (tokenCount + tokens > this.maxTokens - 500) { // Reserve for response
break;
}
context.unshift(message);
tokenCount += tokens;
}
return { context, tokenCount };
},
// Estimate token count
estimateTokens(text) {
// Rough estimation: ~4 characters per token
return Math.ceil(text.length / 4);
}
};// Summarize old messages to save context space
async function summarizeHistory(chat, messageRange) {
const messagesToSummarize = chat.messages.slice(
messageRange.start,
messageRange.end
);
const summaryPrompt = `Summarize the following conversation,
maintaining key plot points, character development, and important details:
${messagesToSummarize.map(m => `${m.role}: ${m.content}`).join('\n')}`;
const summary = await generateSummary(summaryPrompt);
// Create summary message
const summaryMessage = {
role: 'system',
content: `[SUMMARY of messages ${messageRange.start}-${messageRange.end}]: ${summary}`,
type: 'summary',
originalRange: messageRange
};
return summaryMessage;
}In this chapter, you've learned:
- Chat Organization: Managing multiple chats and folders
- Branching: Creating and managing conversation branches
- Message Operations: Editing, regenerating, and swiping
- Bookmarks: Marking and navigating important moments
- Export/Backup: Saving and restoring conversations
- Context Management: Optimizing what gets sent to the AI
- Organization is key: Use tags, folders, and clear names
- Branch freely: Explore alternatives without losing progress
- Bookmark often: Mark important story moments
- Backup regularly: Don't lose your creative work
- Manage context: Summarize old messages to fit more history
Now that you can manage conversations effectively, let's dive into advanced prompting techniques in Chapter 4: Prompt Engineering.
Ready for Chapter 4? Prompt Engineering
Generated for Awesome Code Docs
Most teams struggle here because the hard part is not writing more code, but deciding clear boundaries for chat, messages, message so behavior stays predictable as complexity grows.
In practical terms, this chapter helps you avoid three common failures:
- coupling core logic too tightly to one implementation path
- missing the handoff boundaries between setup, execution, and validation
- shipping changes without clear rollback or observability strategy
After working through this chapter, you should be able to reason about Chapter 3: Chat Management as an operating subsystem inside SillyTavern Tutorial: Advanced LLM Frontend for Power Users, with explicit contracts for inputs, state transitions, and outputs.
Use the implementation notes around chatId, role, content as your checklist when adapting these patterns to your own repository.
Under the hood, Chapter 3: Chat Management usually follows a repeatable control path:
- Context bootstrap: initialize runtime config and prerequisites for
chat. - Input normalization: shape incoming data so
messagesreceives stable contracts. - Core execution: run the main logic branch and propagate intermediate state through
message. - Policy and safety checks: enforce limits, auth scopes, and failure boundaries.
- Output composition: return canonical result payloads for downstream consumers.
- Operational telemetry: emit logs/metrics needed for debugging and performance tuning.
When debugging, walk this sequence in order and confirm each stage has explicit success/failure conditions.
Use the following upstream sources to verify implementation details while reading this chapter:
- GitHub Repository
Why it matters: authoritative reference on
GitHub Repository(github.com). - Extension Directory
Why it matters: authoritative reference on
Extension Directory(github.com). - AI Codebase Knowledge Builder
Why it matters: authoritative reference on
AI Codebase Knowledge Builder(github.com).
Suggested trace strategy:
- search upstream code for
chatandmessagesto map concrete implementation paths - compare docs claims against actual runtime/config code before reusing patterns in production