|
(() => { |
|
// Function to open IndexedDB with the 'keyval-store' database |
|
function openDB() { |
|
return new Promise((resolve, reject) => { |
|
const dbName = 'keyval-store'; // Use 'keyval-store' as the database name |
|
const request = indexedDB.open(dbName); |
|
|
|
request.onsuccess = () => { |
|
const db = request.result; |
|
resolve(db); |
|
}; |
|
request.onerror = () => { |
|
reject(request.error); |
|
}; |
|
}); |
|
} |
|
|
|
// Function to get all chats from the 'keyval' object store |
|
function listChats() { |
|
return new Promise(async (resolve, reject) => { |
|
try { |
|
const db = await openDB(); |
|
const objectStoreName = 'keyval'; // Use 'keyval' as the object store name |
|
|
|
const transaction = db.transaction([objectStoreName], 'readonly'); |
|
const store = transaction.objectStore(objectStoreName); |
|
const chats = []; |
|
|
|
// Open a cursor to iterate over all entries |
|
const request = store.openCursor(); |
|
request.onsuccess = (event) => { |
|
const cursor = event.target.result; |
|
if (cursor) { |
|
const key = cursor.key; |
|
const value = cursor.value; |
|
|
|
// Check if the key corresponds to a chat |
|
if (key.startsWith('CHAT_')) { |
|
chats.push(value); |
|
} |
|
|
|
cursor.continue(); |
|
} else { |
|
resolve(chats); |
|
} |
|
}; |
|
request.onerror = () => { |
|
reject(request.error); |
|
}; |
|
} catch (error) { |
|
reject(error); |
|
} |
|
}); |
|
} |
|
|
|
// Function to list all folders from localStorage |
|
function listFolders() { |
|
return new Promise((resolve) => { |
|
// Load folders from localStorage |
|
const folderListJSON = localStorage.getItem('TM_useFolderList'); |
|
const folders = folderListJSON ? JSON.parse(folderListJSON) : []; |
|
resolve(folders); |
|
}); |
|
} |
|
|
|
// Function to get a single chat by ID from the 'keyval' object store |
|
function getChatByID(db, chatID) { |
|
return new Promise((resolve, reject) => { |
|
const objectStoreName = 'keyval'; // Use 'keyval' as the object store name |
|
|
|
const transaction = db.transaction([objectStoreName], 'readonly'); |
|
const store = transaction.objectStore(objectStoreName); |
|
|
|
const key = `CHAT_${chatID}`; |
|
const request = store.get(key); |
|
|
|
request.onsuccess = () => { |
|
const chat = request.result; |
|
if (chat) { |
|
resolve(chat); |
|
} else { |
|
reject(new Error('Chat not found.')); |
|
} |
|
}; |
|
request.onerror = () => { |
|
reject(request.error); |
|
}; |
|
}); |
|
} |
|
|
|
// Function to update a chat in the 'keyval' object store |
|
function updateChat(db, chat) { |
|
return new Promise((resolve, reject) => { |
|
const objectStoreName = 'keyval'; // Use 'keyval' as the object store name |
|
|
|
const transaction = db.transaction([objectStoreName], 'readwrite'); |
|
const store = transaction.objectStore(objectStoreName); |
|
|
|
const key = `CHAT_${chat.id}`; |
|
|
|
const request = store.put(chat, key); |
|
|
|
request.onsuccess = () => { |
|
resolve(); |
|
}; |
|
request.onerror = () => { |
|
reject(request.error); |
|
}; |
|
}); |
|
} |
|
|
|
// Function to prompt the user to select a folder |
|
function promptUserToSelectFolder(folders) { |
|
return new Promise((resolve) => { |
|
// Create the overlay |
|
const overlay = document.createElement('div'); |
|
overlay.style.position = 'fixed'; |
|
overlay.style.top = '0'; |
|
overlay.style.left = '0'; |
|
overlay.style.width = '100%'; |
|
overlay.style.height = '100%'; |
|
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; |
|
overlay.style.zIndex = '1000'; |
|
overlay.style.display = 'flex'; |
|
overlay.style.alignItems = 'center'; |
|
overlay.style.justifyContent = 'center'; |
|
|
|
// Create the modal |
|
const modal = document.createElement('div'); |
|
modal.style.backgroundColor = '#fff'; |
|
modal.style.color = '#000'; // Ensure text color is readable |
|
modal.style.padding = '20px'; |
|
modal.style.borderRadius = '8px'; |
|
modal.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.3)'; |
|
modal.style.maxWidth = '90%'; |
|
modal.style.maxHeight = '80%'; |
|
modal.style.overflowY = 'auto'; |
|
|
|
// Create the title |
|
const title = document.createElement('h2'); |
|
title.textContent = 'Select a Folder'; |
|
title.style.marginTop = '0'; |
|
modal.appendChild(title); |
|
|
|
// Create the select element |
|
const select = document.createElement('select'); |
|
select.style.width = '100%'; |
|
select.style.marginBottom = '20px'; |
|
select.style.padding = '8px'; |
|
select.style.borderRadius = '4px'; |
|
select.style.border = '1px solid #ccc'; |
|
|
|
// Add an option to unassign the folder |
|
const unassignOption = document.createElement('option'); |
|
unassignOption.value = ''; |
|
unassignOption.textContent = '-- Unassign Folder --'; |
|
select.appendChild(unassignOption); |
|
|
|
// Populate the select options |
|
folders.forEach((folder) => { |
|
const option = document.createElement('option'); |
|
option.value = folder.id; |
|
option.textContent = folder.title; |
|
select.appendChild(option); |
|
}); |
|
|
|
modal.appendChild(select); |
|
|
|
// Create the Cancel and OK buttons |
|
const buttonContainer = document.createElement('div'); |
|
buttonContainer.style.display = 'flex'; |
|
buttonContainer.style.justifyContent = 'flex-end'; |
|
buttonContainer.style.gap = '10px'; |
|
|
|
const cancelButton = document.createElement('button'); |
|
cancelButton.textContent = 'Cancel'; |
|
cancelButton.style.padding = '8px 16px'; |
|
cancelButton.style.border = 'none'; |
|
cancelButton.style.borderRadius = '4px'; |
|
cancelButton.style.backgroundColor = '#f1f1f1'; |
|
cancelButton.style.cursor = 'pointer'; |
|
cancelButton.onclick = () => { |
|
document.body.removeChild(overlay); |
|
resolve(null); |
|
}; |
|
|
|
const okButton = document.createElement('button'); |
|
okButton.textContent = 'OK'; |
|
okButton.style.padding = '8px 16px'; |
|
okButton.style.border = 'none'; |
|
okButton.style.borderRadius = '4px'; |
|
okButton.style.backgroundColor = '#4F46E5'; |
|
okButton.style.color = 'white'; |
|
okButton.style.cursor = 'pointer'; |
|
okButton.onclick = () => { |
|
const selectedFolderID = select.value; |
|
document.body.removeChild(overlay); |
|
resolve(selectedFolderID); |
|
}; |
|
|
|
buttonContainer.appendChild(cancelButton); |
|
buttonContainer.appendChild(okButton); |
|
modal.appendChild(buttonContainer); |
|
|
|
overlay.appendChild(modal); |
|
|
|
document.body.appendChild(overlay); |
|
}); |
|
} |
|
|
|
// Function to add a chat to a folder |
|
async function addChatToFolder(chatOrChatID, folderOrFolderID) { |
|
try { |
|
const db = await openDB(); |
|
|
|
// Determine chat ID |
|
let chatID; |
|
if (typeof chatOrChatID === 'string') { |
|
chatID = chatOrChatID; |
|
} else if (chatOrChatID && chatOrChatID.id) { |
|
chatID = chatOrChatID.id; |
|
} else { |
|
throw new Error('Invalid chat or chat ID.'); |
|
} |
|
|
|
// Determine folder ID |
|
let folderID; |
|
if (typeof folderOrFolderID === 'string') { |
|
folderID = folderOrFolderID; |
|
} else if (folderOrFolderID && folderOrFolderID.id) { |
|
folderID = folderOrFolderID.id; |
|
} else { |
|
throw new Error('Invalid folder or folder ID.'); |
|
} |
|
|
|
// Get the chat |
|
const chat = await getChatByID(db, chatID); |
|
|
|
// Update the folderID of the chat |
|
chat.folderID = folderID; |
|
|
|
await updateChat(db, chat); |
|
|
|
// Optionally, you can trigger an event or refresh the UI to reflect changes |
|
console.log(`Chat '${chat.chatTitle || chat.title || 'Untitled Chat'}' has been moved to folder ID: '${folderID}'.`); |
|
} catch (error) { |
|
console.error('Error adding chat to folder:', error); |
|
} |
|
} |
|
|
|
// Expose the helper functions on window.tmHelpers |
|
window.tmHelpers = { |
|
...(window.tmHelpers || {}), |
|
listFolders, |
|
listChats, |
|
addChatToFolder, // Expose the new helper function |
|
}; |
|
|
|
// Function to categorize the current chat |
|
async function categorizeCurrentChat() { |
|
try { |
|
const chatIDFromURL = window.location.hash.match(/#chat=([^&]+)/); |
|
if (!chatIDFromURL || !chatIDFromURL[1]) { |
|
alert('No chat selected.'); |
|
return; |
|
} |
|
const chatID = chatIDFromURL[1]; |
|
|
|
const db = await openDB(); |
|
|
|
// Load folders from localStorage |
|
const folders = await listFolders(); |
|
|
|
if (folders.length === 0) { |
|
alert('No folders found. Please create a folder first.'); |
|
return; |
|
} |
|
|
|
// Prompt the user with a list of folders |
|
const selectedFolderID = await promptUserToSelectFolder(folders); |
|
|
|
if (selectedFolderID === null) { |
|
// User canceled the prompt |
|
return; |
|
} |
|
|
|
const chat = await getChatByID(db, chatID); |
|
|
|
// Update the folderID of the chat |
|
if (selectedFolderID === '') { |
|
// Unassign folder |
|
delete chat.folderID; |
|
} else { |
|
chat.folderID = selectedFolderID; |
|
} |
|
|
|
await updateChat(db, chat); |
|
|
|
// Confirmation message |
|
const folderName = selectedFolderID |
|
? folders.find((f) => f.id === selectedFolderID)?.title || 'Unknown' |
|
: 'None'; |
|
alert(`Chat has been categorized under folder: '${folderName}'.`); |
|
|
|
// Optionally, trigger an event or refresh the UI to reflect changes |
|
} catch (error) { |
|
console.error('Error categorizing current chat:', error); |
|
alert('An error occurred while categorizing the current chat.'); |
|
} |
|
} |
|
|
|
// Function to add the Categorize button to the sidebar |
|
function addCategorizeButton() { |
|
// Check if button already exists to avoid duplicates |
|
if (document.querySelector('[data-element-id="workspace-tab-categorize"]')) { |
|
return true; |
|
} |
|
|
|
// Find the settings button for reference positioning |
|
const settingsButton = document.querySelector('button[data-element-id="workspace-tab-settings"]'); |
|
if (!settingsButton || !settingsButton.parentElement) { |
|
return false; |
|
} |
|
|
|
// Create custom style for the text to ensure word breaking and matching font |
|
const styleElement = document.createElement('style'); |
|
styleElement.textContent = ` |
|
.categorize-btn-text { |
|
font-family: inherit; |
|
font-size: 11px; |
|
line-height: 1.2; |
|
width: 100%; |
|
text-align: center; |
|
overflow: visible; |
|
word-break: break-word; |
|
hyphens: auto; |
|
padding: 0; |
|
white-space: normal; |
|
display: inline-block; |
|
} |
|
`; |
|
document.head.appendChild(styleElement); |
|
|
|
// Create Categorize button |
|
const categorizeButton = document.createElement('button'); |
|
categorizeButton.setAttribute('data-element-id', 'workspace-tab-categorize'); |
|
categorizeButton.className = 'cursor-default group flex items-center justify-center p-1 text-sm font-medium flex-col group focus:outline-0 focus:text-white text-white/70 text-white/70 hover:bg-white/20 self-stretch h-12 md:h-[50px] px-0.5 py-1.5 rounded-xl flex-col justify-start items-center gap-1.5 flex transition-colors'; |
|
|
|
// Create the icon span |
|
const categorizeIconSpan = document.createElement('span'); |
|
categorizeIconSpan.className = 'block group-hover:bg-white/30 w-[35px] h-[35px] transition-all rounded-lg flex items-center justify-center group-hover:text-white/90'; |
|
|
|
// Folder icon SVG |
|
categorizeIconSpan.innerHTML = ` |
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> |
|
<path d="M3 7V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V9C21 7.89543 20.1046 7 19 7H11L9 5H5C3.89543 5 3 5.89543 3 7Z" stroke-linecap="round" stroke-linejoin="round"/> |
|
</svg> |
|
`; |
|
|
|
// Create text span with custom class |
|
const categorizeTextSpan = document.createElement('span'); |
|
categorizeTextSpan.className = 'font-normal self-stretch text-center text-xs leading-4 md:leading-none categorize-btn-text'; |
|
categorizeTextSpan.innerHTML = 'Add to<br>Folder'; |
|
|
|
// Assemble button |
|
categorizeButton.appendChild(categorizeIconSpan); |
|
categorizeButton.appendChild(categorizeTextSpan); |
|
categorizeButton.onclick = categorizeCurrentChat; |
|
|
|
// Insert button before settings button |
|
settingsButton.parentElement.insertBefore(categorizeButton, settingsButton); |
|
return true; |
|
} |
|
|
|
// Use MutationObserver to watch for when the button can be added |
|
const observer = new MutationObserver(() => { |
|
if (addCategorizeButton()) { |
|
observer.disconnect(); |
|
} |
|
}); |
|
|
|
observer.observe(document.body, { |
|
childList: true, |
|
subtree: true |
|
}); |
|
|
|
// Try to add the button immediately and periodically |
|
if (!addCategorizeButton()) { |
|
const maxAttempts = 10; |
|
let attempts = 0; |
|
const interval = setInterval(() => { |
|
if (addCategorizeButton() || attempts >= maxAttempts) { |
|
clearInterval(interval); |
|
} |
|
attempts++; |
|
}, 1000); |
|
} |
|
})(); |