Skip to content

Latest commit

 

History

History
659 lines (526 loc) · 19.8 KB

File metadata and controls

659 lines (526 loc) · 19.8 KB
layout default
title Chapter 3: Chat Management
parent SillyTavern Tutorial
nav_order 3

Chapter 3: Chat Management

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.

Overview

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

Chat Structure

┌─────────────────────────────────────────────────────────────────┐
│                    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)                                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Managing Multiple Chats

// 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);
    }
  }
};

Conversation Branching

Creating Branches

// 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?"
);

Branch Visualization

┌─────────────────────────────────────────────────────────────────┐
│                    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'    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Merging Branches

// 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);
}

Message Operations

Editing Messages

// 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;
}

Swipe Feature (Alternative Responses)

// 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;
  }
};

Bookmarks and Navigation

Creating Bookmarks

// 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'
};

Quick Navigation

// 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 and Backup

Export Formats

// 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;
  }
};

Automatic Backups

// 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);
      }
    }
  }
};

Context Management

Context Window

// 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);
  }
};

Summarization

// 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;
}

Summary

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

Key Takeaways

  1. Organization is key: Use tags, folders, and clear names
  2. Branch freely: Explore alternatives without losing progress
  3. Bookmark often: Mark important story moments
  4. Backup regularly: Don't lose your creative work
  5. Manage context: Summarize old messages to fit more history

Next Steps

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

What Problem Does This Solve?

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.

How it Works Under the Hood

Under the hood, Chapter 3: Chat Management usually follows a repeatable control path:

  1. Context bootstrap: initialize runtime config and prerequisites for chat.
  2. Input normalization: shape incoming data so messages receives stable contracts.
  3. Core execution: run the main logic branch and propagate intermediate state through message.
  4. Policy and safety checks: enforce limits, auth scopes, and failure boundaries.
  5. Output composition: return canonical result payloads for downstream consumers.
  6. 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.

Source Walkthrough

Use the following upstream sources to verify implementation details while reading this chapter:

Suggested trace strategy:

  • search upstream code for chat and messages to map concrete implementation paths
  • compare docs claims against actual runtime/config code before reusing patterns in production

Chapter Connections