sapbot

LLM Database Maker Tool

Feb 6th, 2026
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 25.17 KB | Source Code | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>LLM Database Maker</title>
  7.     <script src="https://cdn.tailwindcss.com"></script>
  8.     <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
  9.     <style>
  10.         /* Custom Scrollbar */
  11.         ::-webkit-scrollbar {
  12.             width: 6px;
  13.             height: 6px;
  14.         }
  15.         ::-webkit-scrollbar-track {
  16.             background: #f1f1f1;
  17.         }
  18.         ::-webkit-scrollbar-thumb {
  19.             background: #c1c1c1;
  20.             border-radius: 3px;
  21.         }
  22.         ::-webkit-scrollbar-thumb:hover {
  23.             background: #a8a8a8;
  24.         }
  25.  
  26.         /* Chat Bubble Styles */
  27.         .message-bubble {
  28.             max-width: 70%;
  29.             padding: 10px 14px;
  30.             border-radius: 8px;
  31.             position: relative;
  32.             font-size: 0.95rem;
  33.             line-height: 1.4;
  34.             word-wrap: break-word;
  35.             box-shadow: 0 1px 2px rgba(0,0,0,0.08);
  36.             transition: all 0.2s;
  37.             white-space: pre-wrap; /* CRITICAL: Preserves newlines and spaces */
  38.         }
  39.        
  40.         .message-row:hover .message-actions {
  41.             opacity: 1;
  42.         }
  43.  
  44.         /* WhatsApp-like tails */
  45.         .msg-user {
  46.             background-color: #dcf8c6; /* Light Green */
  47.             border-top-right-radius: 0;
  48.             margin-left: auto;
  49.         }
  50.         .msg-assistant {
  51.             background-color: #ffffff; /* White */
  52.             border-top-left-radius: 0;
  53.             border: 1px solid #f0f0f0;
  54.             margin-right: auto;
  55.         }
  56.  
  57.         /* System Message */
  58.         .msg-system {
  59.             background-color: #e3f2fd;
  60.             max-width: 90%;
  61.             margin: 0 auto;
  62.             font-size: 0.85rem;
  63.             color: #555;
  64.             text-align: center;
  65.             border-radius: 8px;
  66.         }
  67.  
  68.         /* Animations */
  69.         @keyframes slideIn {
  70.             from { opacity: 0; transform: translateY(10px); }
  71.             to { opacity: 1; transform: translateY(0); }
  72.         }
  73.         .animate-slide-in {
  74.             animation: slideIn 0.3s ease-out forwards;
  75.         }
  76.  
  77.         /* Hide file input */
  78.         input[type="file"] {
  79.             display: none;
  80.         }
  81.  
  82.         /* Editable content focus */
  83.         [contenteditable]:focus {
  84.             outline: 2px solid #3b82f6;
  85.             background-color: #fff;
  86.             padding: 2px;
  87.             border-radius: 4px;
  88.         }
  89.     </style>
  90. </head>
  91. <body class="bg-[#e5ddd5] h-screen w-screen overflow-hidden font-sans text-gray-800 flex flex-col">
  92.  
  93.     <!-- Top Navigation / Header -->
  94.     <header class="bg-[#128c7e] text-white h-16 flex items-center justify-between px-6 shadow-md z-20 shrink-0">
  95.         <div class="flex items-center gap-3">
  96.             <i class="fa-solid fa-database text-xl"></i>
  97.             <h1 class="font-semibold text-lg tracking-wide">LLM Database Maker</h1>
  98.         </div>
  99.         <div class="flex gap-3">
  100.             <button onclick="app.exportJSONL()" class="bg-[#075e54] hover:bg-[#054c44] text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2 shadow-sm">
  101.                 <i class="fa-solid fa-download"></i> Export JSONL
  102.             </button>
  103.             <label class="bg-[#075e54] hover:bg-[#054c44] text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2 shadow-sm cursor-pointer">
  104.                 <i class="fa-solid fa-upload"></i> Import JSONL
  105.                 <input type="file" id="fileInput" accept=".jsonl,.json,.txt" onchange="app.importJSONL(this)">
  106.             </label>
  107.         </div>
  108.     </header>
  109.  
  110.     <!-- Main Layout -->
  111.     <div class="flex flex-1 overflow-hidden">
  112.        
  113.         <!-- LEFT SIDEBAR: Conversation List -->
  114.         <aside class="w-1/3 min-w-[300px] max-w-[400px] bg-white border-r border-gray-300 flex flex-col z-10 shadow-lg">
  115.             <!-- Sidebar Header -->
  116.             <div class="p-4 bg-[#f0f2f5] border-b border-gray-300 flex justify-between items-center">
  117.                 <span class="font-semibold text-gray-600">Conversations</span>
  118.                 <button onclick="app.addConversation()" class="text-[#128c7e] hover:bg-gray-200 p-2 rounded-full transition" title="New Conversation">
  119.                     <i class="fa-solid fa-plus text-lg"></i>
  120.                 </button>
  121.             </div>
  122.  
  123.             <!-- Search/Filter -->
  124.             <div class="p-2 bg-[#f0f2f5]">
  125.                 <div class="bg-white rounded-lg flex items-center px-3 py-2 shadow-sm">
  126.                     <i class="fa-solid fa-search text-gray-400 text-sm mr-2"></i>
  127.                     <input type="text" id="searchConv" placeholder="Search conversations..." class="w-full outline-none text-sm text-gray-700 bg-transparent" oninput="app.renderSidebar()">
  128.                 </div>
  129.             </div>
  130.  
  131.             <!-- List Container -->
  132.             <div id="conversationList" class="flex-1 overflow-y-auto bg-white">
  133.                 <!-- Items injected via JS -->
  134.             </div>
  135.            
  136.             <!-- Stats Footer -->
  137.             <div class="p-2 bg-[#f0f2f5] text-xs text-gray-500 text-center border-t border-gray-300">
  138.                 <span id="totalStats">0 Conversations</span>
  139.             </div>
  140.         </aside>
  141.  
  142.         <!-- RIGHT PANEL: Chat Interface -->
  143.         <main class="flex-1 flex flex-col bg-[#e5ddd5] relative">
  144.            
  145.             <!-- Chat Header -->
  146.             <div class="h-16 bg-[#f0f2f5] border-b border-gray-300 flex items-center justify-between px-6 shadow-sm z-10 shrink-0">
  147.                 <div class="flex items-center gap-3">
  148.                     <div class="w-10 h-10 rounded-full bg-gray-300 flex items-center justify-center text-gray-600">
  149.                         <i class="fa-solid fa-robot"></i>
  150.                     </div>
  151.                     <div>
  152.                         <h2 class="font-semibold text-gray-800" id="activeConvTitle">Select a Conversation</h2>
  153.                         <p class="text-xs text-gray-500" id="activeConvMeta">--</p>
  154.                     </div>
  155.                 </div>
  156.                 <div class="flex gap-2">
  157.                     <button onclick="app.toggleSystemPrompt()" class="text-gray-500 hover:text-[#128c7e] p-2 transition" title="Edit System Prompt">
  158.                         <i class="fa-solid fa-sliders"></i>
  159.                     </button>
  160.                     <button onclick="app.deleteActiveConversation()" class="text-gray-500 hover:text-red-600 p-2 transition" title="Delete Conversation">
  161.                         <i class="fa-solid fa-trash"></i>
  162.                     </button>
  163.                 </div>
  164.             </div>
  165.  
  166.             <!-- System Prompt Editor (Hidden by default) -->
  167.             <div id="systemPromptEditor" class="hidden bg-blue-50 border-b border-blue-200 p-3 flex flex-col gap-2 animate-slide-in">
  168.                 <div class="flex justify-between items-center">
  169.                     <label class="text-xs font-bold text-blue-800 uppercase">System Prompt (Context)</label>
  170.                     <button onclick="app.toggleSystemPrompt()" class="text-blue-600 text-xs hover:underline">Close</button>
  171.                 </div>
  172.                 <textarea id="systemInput" class="w-full p-2 text-sm border border-blue-200 rounded focus:outline-none focus:border-blue-500 bg-white" rows="2" placeholder="e.g., You are a helpful assistant..."></textarea>
  173.                 <button onclick="app.saveSystemPrompt()" class="self-end bg-blue-600 text-white px-3 py-1 rounded text-xs hover:bg-blue-700">Set Context</button>
  174.             </div>
  175.  
  176.             <!-- Messages Area -->
  177.             <div id="chatContainer" class="flex-1 overflow-y-auto p-6 space-y-4 relative">
  178.                 <!-- Empty State -->
  179.                 <div id="emptyState" class="absolute inset-0 flex flex-col items-center justify-center text-gray-400 pointer-events-none">
  180.                     <i class="fa-solid fa-comments text-6xl mb-4 opacity-20"></i>
  181.                     <p>Select or create a conversation to start editing</p>
  182.                 </div>
  183.                 <!-- Messages injected here -->
  184.             </div>
  185.  
  186.             <!-- Input Area -->
  187.             <div class="bg-[#f0f2f5] p-3 border-t border-gray-300 shrink-0">
  188.                 <div class="flex flex-col gap-2 max-w-4xl mx-auto">
  189.                    
  190.                     <!-- Role Selector -->
  191.                     <div class="flex justify-center gap-4 mb-1">
  192.                         <label class="flex items-center gap-2 cursor-pointer group">
  193.                             <input type="radio" name="role" value="user" checked class="accent-[#128c7e]">
  194.                             <span class="text-sm font-medium text-gray-700 group-hover:text-[#128c7e]">User</span>
  195.                         </label>
  196.                         <label class="flex items-center gap-2 cursor-pointer group">
  197.                             <input type="radio" name="role" value="assistant" class="accent-[#128c7e]">
  198.                             <span class="text-sm font-medium text-gray-700 group-hover:text-[#128c7e]">Assistant (AI)</span>
  199.                         </label>
  200.                     </div>
  201.  
  202.                     <!-- Text Input -->
  203.                     <div class="flex items-end bg-white rounded-lg shadow-sm border border-gray-300 focus-within:border-[#128c7e] transition-colors">
  204.                         <textarea id="messageInput" rows="1" class="w-full p-3 outline-none resize-none bg-transparent text-gray-700 max-h-32" placeholder="Type a message..." onkeydown="app.handleKeyDown(event)"></textarea>
  205.                         <button onclick="app.addMessage()" class="p-3 text-[#128c7e] hover:bg-gray-100 rounded-r-lg transition">
  206.                             <i class="fa-solid fa-paper-plane"></i>
  207.                         </button>
  208.                     </div>
  209.                 </div>
  210.             </div>
  211.         </main>
  212.     </div>
  213.  
  214.     <!-- JavaScript Logic -->
  215.     <script>
  216.         const app = {
  217.             data: [], // Array of conversations - EMPTY BY DEFAULT NOW
  218.             activeId: null,
  219.            
  220.             init() {
  221.                 // No sample data added by default
  222.                 this.renderSidebar();
  223.                
  224.                 // Auto-resize textarea
  225.                 const textarea = document.getElementById('messageInput');
  226.                 textarea.addEventListener('input', function() {
  227.                     this.style.height = 'auto';
  228.                     this.style.height = (this.scrollHeight) + 'px';
  229.                     if(this.value === '') this.style.height = 'auto';
  230.                 });
  231.             },
  232.  
  233.             // --- Data Management ---
  234.  
  235.             generateId() {
  236.                 return Date.now().toString(36) + Math.random().toString(36).substr(2);
  237.             },
  238.  
  239.             addConversation(title = null, messages = []) {
  240.                 const id = this.generateId();
  241.                 const newConv = {
  242.                     id: id,
  243.                     title: title || `Conversation ${this.data.length + 1}`,
  244.                     timestamp: new Date(),
  245.                     systemPrompt: "",
  246.                     messages: messages
  247.                 };
  248.                 this.data.unshift(newConv); // Add to top
  249.                 this.activeId = id;
  250.                 this.renderSidebar();
  251.                 this.renderChat();
  252.             },
  253.  
  254.             deleteActiveConversation() {
  255.                 if (!this.activeId) return;
  256.                 if(confirm("Delete this conversation permanently?")) {
  257.                     this.data = this.data.filter(c => c.id !== this.activeId);
  258.                     this.activeId = null;
  259.                     this.renderSidebar();
  260.                     this.renderChat();
  261.                 }
  262.             },
  263.  
  264.             getActiveConversation() {
  265.                 return this.data.find(c => c.id === this.activeId);
  266.             },
  267.  
  268.             // --- UI Rendering ---
  269.  
  270.             renderSidebar() {
  271.                 const list = document.getElementById('conversationList');
  272.                 const search = document.getElementById('searchConv').value.toLowerCase();
  273.                 list.innerHTML = '';
  274.  
  275.                 let filtered = this.data.filter(c => c.title.toLowerCase().includes(search));
  276.  
  277.                 filtered.forEach(conv => {
  278.                     const isActive = conv.id === this.activeId;
  279.                     const lastMsg = conv.messages.length > 0 ? conv.messages[conv.messages.length - 1].content : "No messages";
  280.                     // Handle multiline preview in sidebar
  281.                     const singleLine = lastMsg.replace(/\n/g, ' ');
  282.                     const truncated = singleLine.length > 40 ? singleLine.substring(0, 40) + '...' : singleLine;
  283.                    
  284.                     const div = document.createElement('div');
  285.                     div.className = `p-3 border-b border-gray-100 cursor-pointer hover:bg-[#f5f6f6] transition flex flex-col gap-1 ${isActive ? 'bg-[#e9edef] border-l-4 border-l-[#128c7e]' : ''}`;
  286.                     div.onclick = () => {
  287.                         this.activeId = conv.id;
  288.                         this.renderSidebar();
  289.                         this.renderChat();
  290.                     };
  291.  
  292.                     div.innerHTML = `
  293.                         <div class="flex justify-between items-baseline">
  294.                             <h3 class="font-medium text-gray-800 text-sm">${conv.title}</h3>
  295.                             <span class="text-[10px] text-gray-400">${new Date(conv.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
  296.                         </div>
  297.                         <p class="text-xs text-gray-500 truncate">${truncated}</p>
  298.                         <div class="flex gap-1 mt-1">
  299.                             <span class="text-[10px] bg-gray-200 text-gray-600 px-1 rounded">${conv.messages.length} msgs</span>
  300.                             ${conv.systemPrompt ? '<span class="text-[10px] bg-blue-100 text-blue-600 px-1 rounded">SYS</span>' : ''}
  301.                         </div>
  302.                     `;
  303.                     list.appendChild(div);
  304.                 });
  305.  
  306.                 document.getElementById('totalStats').innerText = `${this.data.length} Conversations`;
  307.             },
  308.  
  309.             renderChat() {
  310.                 const container = document.getElementById('chatContainer');
  311.                 const emptyState = document.getElementById('emptyState');
  312.                 const conv = this.getActiveConversation();
  313.  
  314.                 // Update Header
  315.                 if (conv) {
  316.                     document.getElementById('activeConvTitle').innerText = conv.title;
  317.                     document.getElementById('activeConvMeta').innerText = `${conv.messages.length} messages`;
  318.                     document.getElementById('systemInput').value = conv.systemPrompt || "";
  319.                     emptyState.style.display = 'none';
  320.                 } else {
  321.                     document.getElementById('activeConvTitle').innerText = "Select a Conversation";
  322.                     document.getElementById('activeConvMeta').innerText = "--";
  323.                     emptyState.style.display = 'flex';
  324.                     container.innerHTML = '';
  325.                     container.appendChild(emptyState);
  326.                     return;
  327.                 }
  328.  
  329.                 // Clear and Rebuild Messages
  330.                 container.innerHTML = '';
  331.                 container.appendChild(emptyState); // Keep it in DOM but hidden
  332.  
  333.                 // Render System Prompt if exists (Visual indicator only)
  334.                 if (conv.systemPrompt) {
  335.                     const sysDiv = document.createElement('div');
  336.                     sysDiv.className = "flex justify-center mb-4";
  337.                     sysDiv.innerHTML = `
  338.                         <div class="bg-blue-100 text-blue-800 text-xs px-3 py-1 rounded-full border border-blue-200 flex items-center gap-2">
  339.                             <i class="fa-solid fa-info-circle"></i> System Context Set
  340.                         </div>
  341.                     `;
  342.                     container.appendChild(sysDiv);
  343.                 }
  344.  
  345.                 conv.messages.forEach((msg, index) => {
  346.                     const isUser = msg.role === 'user';
  347.                     const row = document.createElement('div');
  348.                     row.className = `flex w-full message-row group ${isUser ? 'justify-end' : 'justify-start'} animate-slide-in`;
  349.                    
  350.                     // Actions (Edit/Delete) - visible on hover
  351.                     const actions = `
  352.                         <div class="message-actions opacity-0 transition-opacity flex gap-2 mx-2 items-center text-gray-400 text-xs">
  353.                             <button onclick="app.editMessage(${index})" class="hover:text-blue-500" title="Edit"><i class="fa-solid fa-pen"></i></button>
  354.                             <button onclick="app.deleteMessage(${index})" class="hover:text-red-500" title="Delete"><i class="fa-solid fa-times"></i></button>
  355.                         </div>
  356.                     `;
  357.  
  358.                     const bubble = document.createElement('div');
  359.                     bubble.className = `message-bubble relative ${isUser ? 'msg-user' : 'msg-assistant'}`;
  360.                    
  361.                     // Contenteditable for inline editing
  362.                     // NOTE: Using textContent to preserve newlines correctly in the editable div
  363.                     bubble.innerHTML = `
  364.                         <div class="text-xs font-bold mb-1 opacity-50 uppercase tracking-wider">${msg.role}</div>
  365.                         <div contenteditable="true" onblur="app.updateMessage(${index}, this.innerText)" class="outline-none whitespace-pre-wrap">${msg.content}</div>
  366.                     `;
  367.  
  368.                     if (isUser) {
  369.                         row.innerHTML = actions;
  370.                         row.appendChild(bubble);
  371.                     } else {
  372.                         row.appendChild(bubble);
  373.                         row.innerHTML += actions;
  374.                     }
  375.  
  376.                     container.appendChild(row);
  377.                 });
  378.  
  379.                 // Scroll to bottom
  380.                 container.scrollTop = container.scrollHeight;
  381.             },
  382.  
  383.             // --- Actions ---
  384.  
  385.             handleKeyDown(e) {
  386.                 if (e.key === 'Enter' && !e.shiftKey) {
  387.                    e.preventDefault();
  388.                     this.addMessage();
  389.                 }
  390.             },
  391.  
  392.             addMessage() {
  393.                 const input = document.getElementById('messageInput');
  394.                 const text = input.value; // Don't trim here to preserve leading newlines if user wants them (though usually not desired)
  395.                 if (!text.trim() || !this.activeId) return; // Check trim only for validation
  396.  
  397.                 const role = document.querySelector('input[name="role"]:checked').value;
  398.                 const conv = this.getActiveConversation();
  399.                
  400.                 conv.messages.push({
  401.                     role: role,
  402.                     content: text // Store raw text with newlines
  403.                 });
  404.  
  405.                 // Update timestamp
  406.                 conv.timestamp = new Date();
  407.  
  408.                 input.value = '';
  409.                 input.style.height = 'auto';
  410.                
  411.                 this.renderChat();
  412.                 this.renderSidebar(); // Update preview
  413.             },
  414.  
  415.             updateMessage(index, newText) {
  416.                 const conv = this.getActiveConversation();
  417.                 if (conv) {
  418.                     conv.messages[index].content = newText; // Preserve whitespace from contenteditable
  419.                     this.renderSidebar(); // Update preview if it was the last msg
  420.                 }
  421.             },
  422.  
  423.             deleteMessage(index) {
  424.                 const conv = this.getActiveConversation();
  425.                 if (conv) {
  426.                     conv.messages.splice(index, 1);
  427.                     this.renderChat();
  428.                     this.renderSidebar();
  429.                 }
  430.             },
  431.            
  432.             editMessage(index) {
  433.                 // Focus is handled by contenteditable, this is just a trigger if needed
  434.                 // But we can use this to maybe scroll to it or highlight
  435.             },
  436.  
  437.             toggleSystemPrompt() {
  438.                 const editor = document.getElementById('systemPromptEditor');
  439.                 editor.classList.toggle('hidden');
  440.             },
  441.  
  442.             saveSystemPrompt() {
  443.                 const conv = this.getActiveConversation();
  444.                 if (conv) {
  445.                     conv.systemPrompt = document.getElementById('systemInput').value.trim();
  446.                     this.renderChat();
  447.                     this.renderSidebar();
  448.                     this.toggleSystemPrompt();
  449.                 }
  450.             },
  451.  
  452.             // --- Import / Export ---
  453.  
  454.             exportJSONL() {
  455.                 if (this.data.length === 0) {
  456.                     alert("No data to export!");
  457.                     return;
  458.                 }
  459.  
  460.                 let output = "";
  461.                
  462.                 this.data.forEach(conv => {
  463.                     const messages = [];
  464.                    
  465.                     // Add system prompt if exists as first message
  466.                     if (conv.systemPrompt) {
  467.                         messages.push({ role: "system", content: conv.systemPrompt });
  468.                     }
  469.                    
  470.                     // Add rest
  471.                     messages.push(...conv.messages);
  472.  
  473.                     // Convert to JSON string
  474.                     const line = JSON.stringify(messages);
  475.                     output += line + "\n";
  476.                 });
  477.  
  478.                 const blob = new Blob([output], { type: "application/jsonl" });
  479.                 const url = URL.createObjectURL(blob);
  480.                 const a = document.createElement('a');
  481.                 a.href = url;
  482.                 a.download = `llm_dataset_${new Date().toISOString().slice(0,10)}.jsonl`;
  483.                 document.body.appendChild(a);
  484.                 a.click();
  485.                 document.body.removeChild(a);
  486.                 URL.revokeObjectURL(url);
  487.             },
  488.  
  489.             importJSONL(input) {
  490.                 const file = input.files[0];
  491.                 if (!file) return;
  492.  
  493.                 const reader = new FileReader();
  494.                 reader.onload = (e) => {
  495.                     try {
  496.                         const text = e.target.result;
  497.                         const lines = text.split(/\r?\n/).filter(line => line.trim() !== '');
  498.                        
  499.                         let importCount = 0;
  500.  
  501.                         lines.forEach(line => {
  502.                             try {
  503.                                 const messages = JSON.parse(line);
  504.                                 if (Array.isArray(messages) && messages.length > 0) {
  505.                                    // Extract system prompt if present
  506.                                    let systemPrompt = "";
  507.                                     let chatMessages = [];
  508.                                    
  509.                                     messages.forEach(m => {
  510.                                         if (m.role === 'system') {
  511.                                             systemPrompt = m.content;
  512.                                         } else {
  513.                                             chatMessages.push(m);
  514.                                         }
  515.                                     });
  516.  
  517.                                     // Create new conversation
  518.                                     const id = this.generateId();
  519.                                     this.data.unshift({
  520.                                         id: id,
  521.                                         title: `Imported ${new Date().toLocaleTimeString()}`,
  522.                                         timestamp: new Date(),
  523.                                         systemPrompt: systemPrompt,
  524.                                         messages: chatMessages
  525.                                     });
  526.                                     importCount++;
  527.                                 }
  528.                             } catch (err) {
  529.                                 console.warn("Skipping invalid line:", line);
  530.                             }
  531.                         });
  532.  
  533.                         if (importCount > 0) {
  534.                             this.activeId = this.data[0].id; // Select first imported
  535.                             this.renderSidebar();
  536.                             this.renderChat();
  537.                             alert(`Successfully imported ${importCount} conversations!`);
  538.                         } else {
  539.                             alert("No valid conversations found in file.");
  540.                         }
  541.  
  542.                     } catch (err) {
  543.                         alert("Error reading file: " + err.message);
  544.                     }
  545.                     // Reset input
  546.                     input.value = '';
  547.                 };
  548.                 reader.readAsText(file);
  549.             }
  550.         };
  551.  
  552.         // Initialize
  553.         document.addEventListener('DOMContentLoaded', () => {
  554.             app.init();
  555.         });
  556.     </script>
  557. </body>
  558. </html>
Add Comment
Please, Sign In to add comment