Loominary Documentation

repository·main·Indexed 19 days ago

https://github.com/laumss/loominary

A local-first conversation manager for archiving, searching, and exporting chat histories from AI platforms including Claude, ChatGPT, Gemini, Grok, NotebookLM, Google AI Studio, and SillyTavern. Features include realtime branch recording, semantic search, batch exports to Markdown or PDF, and developer utilities for data processing, i18n, and UUID management.

Tokens
12.6K
Snippets
50
Records
61
Agent score
66%

What's inside Loominary

  1. Overview of Loominary

    main

    Loominary is a local-first conversation manager designed to archive, search, and manage AI chat histories from multiple platforms. It allows you to load chats from Claude, ChatGPT, Gemini, Grok, NotebookLM, Google AI Studio, or SillyTavern into a local archive that you control.

    Key capabilities include:

    • Global Search: Search by content, title, or semantic meaning (via custom embedding models).
    • Branch Navigation: Automatically detects and visualizes conversation branches on a timeline.
    • Tagging: Mark messages as completed, important, or deleted.
    • Flexible Exports: Export to Markdown or PDF, with options to include timestamps, thinking processes, Artifacts, tool calls, or citations.
    • Batch Processing: Export hundreds of conversations at once into a ZIP file.
    • Realtime Recording: Captures every version of a prompt and response, preventing data loss when prompts are edited or regenerated.
  2. Extending Loominary for Developers

    main

    While the online version is browser-only, building Loominary locally allows you to run a backend that serves archived conversation data to other local tools.

    Planned integration patterns include:

    • MCP (Model Context Protocol): Exposing tags, memories, project instructions, and conversation history to local AI clients.
    • Local Context Injection: Using archived Claude project context or marked 'important' conversations as context for local AI workflows.
  3. Enable Realtime Recording

    main
    To prevent losing data when you edit a prompt or regenerate a response, enable Realtime Recording. Once enabled, Loominary records every version of the conversation in the background. You can view these versions as a tree of branches in the preview mode, allowing you to compare different iterations of a prompt or response.
  4. Supported Platforms and Features

    main

    Loominary supports various features depending on the platform being used. Use the following compatibility matrix to understand what you can archive:

    PlatformLoad ChatsProject & MemoriesRealtime Branches
    ClaudeYesYesYes
    ChatGPTYesYes
    GeminiYesYes
    GrokYesYes
    NotebookLMYesWhiteboard mode
    Google AI StudioYes
    SillyTavernYesYesMerged branch files
  5. Render Table of Contents (TOC) with page links

    main

    The renderTOCWithLinks(tocPage, messages) method generates a clickable Table of Contents page.

    • Structure: Displays a 'Table of Contents' title and a separator line.
    • Entries: For each message anchor, it renders the index, sender (Human/Assistant), and an optional branch marker (e.g., [Branch 2]).
    • Interactivity: Both the entry text and the page number (e.g., p.5) are rendered as clickable links that jump to the specific page.
    • Preview: Includes a truncated 50-character preview of the message content below the entry.
    • Styling: Uses specific colors for Human vs Assistant senders.
    /**
       * 渲染目录(Table of Contents)带页码链接
       */
      renderTOCWithLinks(tocPage, messages) {
        // ... implementation details ...
      }
  6. Remove or clear renames

    main

    Use the following methods to manage the lifecycle of renames:

    • removeRename(uuid): Deletes the custom name for a specific uuid and saves the change.
    • clearAllRenames(): Wipes all custom renames from the manager and clears storage.
    renameManager.removeRename(uuid);
    renameManager.clearAllRenames();
  7. Detect conversation branches with detectBranches()

    main

    After data has been extracted into a unified format, use detectBranches(processedData) to identify if the conversation contains multiple branches (e.g., different response paths or 'swipes').

    This function checks the format property of the processedData and applies the appropriate branch detection logic for:

    • claude / claude_code
    • chatgpt
    • grok
    • copilot / jsonl_chat / gemini_notebooklm (using generic branch detection)

    If no chat_history is present in the processed data, it returns the data unchanged.

    import { extractChatData, detectBranches } from './src/utils/fileParser/index.js';
    
    const rawData = { /* ... */ };
    const processed = extractChatData(rawData);
    const branchedData = detectBranches(processed);
  8. Parse UUIDs to retrieve file and conversation context

    main

    Use parseUuid(uuid) to decompose a Loominary UUID back into its constituent parts. This is useful when you have a unique identifier and need to determine which file and which specific conversation it belongs to.

    Return Value

    The function returns an object with the following shape:

    • fileHash: The extracted hash string (or null).
    • conversationUuid: The extracted conversation identifier (or null).

    Parsing Logic

    • If the UUID starts with file-, it is treated as a file-level identifier (returns fileHash and null for conversationUuid).
    • Otherwise, it attempts to split the string by hyphens to separate the fileHash from the conversationUuid.
    import { parseUuid } from './utils/data/uuidManager';
    
    // Parsing a file-only UUID
    const fileResult = parseUuid('file-a1b2c3');
    // Returns: { fileHash: 'a1b2c3', conversationUuid: null }
    
    // Parsing a conversation-specific UUID
    const convResult = parseUuid('a1b2c3-conv-123');
    // Returns: { fileHash: 'a1b2c3', conversationUuid: 'conv-123' }
  9. Render PDF footer

    main

    The renderFooter(pageNumber, totalPages) method adds a consistent footer to every page.

    • Layout: Draws a horizontal separator line near the bottom.
    • Left Side: Displays the export timestamp (Exported: {exportDate}).
    • Right Side: Displays the current page and total pages (e.g., 1 / 10).
    • Styling: Uses FONT_SIZE_FOOTER and COLOR_FOOTER styles.
    /**
       * 渲染页脚
       */
      renderFooter(pageNumber, totalPages) {
        // ... implementation details ...
      }
  10. Get branch markers with getBranchMarker()

    main

    Returns a visual marker indicating the branching state of a message in a conversation tree.

    • If msg.is_branch_point is true, returns 🔀.
    • If msg.branch_level > 0, returns a path indicator like ↳2-1 (derived from branch_id) or ↳[level].
    • Otherwise, returns an empty string.
    import { getBranchMarker } from './utils/formatHelpers.js';
    
    // Branch point
    getBranchMarker({ is_branch_point: true }); // ' 🔀'
    
    // Nested branch (Claude/ChatGPT style)
    getBranchMarker({ branch_level: 2, branch_id: 'main.2.1' }); // ' ↳2-1'
    
    // Nested branch (Grok style)
    getBranchMarker({ branch_level: 2, branch_id: 'main_alt1_alt2' }); // ' ↳1-2'
  11. Count words with countWords()

    main

    Calculates the word count of a string. It is designed to support both Western languages (using word boundaries) and CJK (Chinese, Japanese, Korean) characters by counting individual CJK characters as words.

    import { countWords } from './utils/textUtils';
    
    const count = countWords('Hello world 你好');
    // Returns: 4 (2 western words + 2 CJK characters)