Spaces:
Running
Running
| /** | |
| * history.js - プロンプト生成履歴の管理 | |
| * | |
| * 主な機能: | |
| * - 履歴の保存と読み込み | |
| * - 履歴の検索 | |
| * - 履歴項目の編集と削除 | |
| * - ストレージ使用量の表示 | |
| * | |
| * 重要な関数: | |
| * - saveToHistory(): 新しい履歴項目を保存 | |
| * - updateHistoryList(): 履歴リストを更新 | |
| * - filterHistory(): 履歴の検索機能 | |
| * - loadHistoryItem(): 履歴項目を読み込み | |
| * | |
| * 注意事項: | |
| * - 履歴の最大サイズは3MB | |
| * - 容量超過時は古い履歴から自動削除 | |
| */ | |
| function saveToHistory(title) { | |
| if(!title) { | |
| title = document.getElementById('query').value.slice(0, 10); | |
| } | |
| const historyItem = { | |
| query: document.getElementById('query').value, | |
| promptEn: document.getElementById('promptEn').value, | |
| promptMyLanguage: document.getElementById('promptMyLanguage').value, | |
| danbooruTags: document.getElementById('danbooruTags').value, | |
| timestamp: new Date().toISOString(), | |
| title: title | |
| }; | |
| let history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]'); | |
| history.unshift(historyItem); | |
| // 履歴の合計サイズが3MB以下になるまで古い項目を削除 | |
| while (JSON.stringify(history).length > 3 * 1024 * 1024) { | |
| history.pop(); | |
| } | |
| localStorage.setItem('gemini_prompt_history', JSON.stringify(history)); | |
| updateHistoryList(); | |
| } | |
| function updateHistoryList() { | |
| const historyList = document.getElementById('historyList'); | |
| const noHistoryMessage = document.getElementById('noHistoryMessage'); | |
| historyList.innerHTML = ''; | |
| const history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]'); | |
| if (history.length === 0) { | |
| noHistoryMessage.classList.remove('d-none'); | |
| historyList.classList.add('d-none'); | |
| } else { | |
| noHistoryMessage.classList.add('d-none'); | |
| historyList.classList.remove('d-none'); | |
| // 容量プログレスバーを追加 | |
| const storageUsage = JSON.stringify(history).length; | |
| const storageLimit = 3 * 1024 * 1024; // 3MB | |
| const usagePercentage = (storageUsage / storageLimit) * 100; | |
| const progressBarContainer = document.createElement('div'); | |
| progressBarContainer.className = 'mb-3'; | |
| progressBarContainer.innerHTML = ` | |
| <div class="progress" style="height: 24px; border: 1px solid #dee2e6; position: relative;"> | |
| <div class="progress-bar ${usagePercentage > 90 ? 'bg-danger' : 'bg-primary'}" role="progressbar" | |
| style="width: ${usagePercentage}%;" aria-valuenow="${usagePercentage}" aria-valuemin="0" aria-valuemax="100"></div> | |
| <div class="position-absolute w-100 h-100 d-flex align-items-center justify-content-center" style="top: 0; left: 0;"> | |
| <span style="color: white; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;"> | |
| ${(storageUsage / 1024 / 1024).toFixed(2)}MB / ${storageLimit / 1024 / 1024}MB | |
| </span> | |
| </div> | |
| </div> | |
| `; | |
| historyList.appendChild(progressBarContainer); | |
| // 検索フォームを追加 | |
| const searchForm = document.createElement('div'); | |
| searchForm.className = 'mb-3'; | |
| searchForm.innerHTML = ` | |
| <input type="text" class="form-control" id="historySearchInput" placeholder="履歴を検索..."> | |
| `; | |
| historyList.appendChild(searchForm); | |
| history.forEach((item, index) => { | |
| const li = document.createElement('li'); | |
| li.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-start'; | |
| li.dataset.item = JSON.stringify(item); | |
| const contentDiv = document.createElement('div'); | |
| contentDiv.className = 'ms-2 me-auto'; | |
| contentDiv.style.cursor = 'pointer'; | |
| contentDiv.onclick = () => loadHistoryItem(index); | |
| const titleDiv = document.createElement('div'); | |
| titleDiv.className = 'fw-bold text-truncate'; | |
| titleDiv.textContent = item.title || item.query.slice(0, 10); | |
| const dateDiv = document.createElement('div'); | |
| dateDiv.className = 'small text-muted'; | |
| dateDiv.textContent = new Date(item.timestamp).toLocaleString(); | |
| contentDiv.appendChild(titleDiv); | |
| contentDiv.appendChild(dateDiv); | |
| const buttonsContainer = document.createElement('div'); | |
| buttonsContainer.className = 'd-flex'; | |
| const editButton = document.createElement('button'); | |
| editButton.className = 'btn btn-secondary btn-sm me-2'; | |
| editButton.innerHTML = '<i class="fas fa-pencil-alt"></i>'; | |
| editButton.onclick = (e) => { | |
| e.stopPropagation(); | |
| editHistoryItemTitle(index, titleDiv); | |
| }; | |
| const deleteButton = document.createElement('button'); | |
| deleteButton.className = 'btn btn-danger btn-sm'; | |
| deleteButton.innerHTML = '<i class="fas fa-trash"></i>'; | |
| deleteButton.onclick = (e) => { | |
| e.stopPropagation(); | |
| deleteHistoryItem(index); | |
| }; | |
| buttonsContainer.appendChild(editButton); | |
| buttonsContainer.appendChild(deleteButton); | |
| li.appendChild(contentDiv); | |
| li.appendChild(buttonsContainer); | |
| historyList.appendChild(li); | |
| contentDiv.style.width = `calc(100% - ${buttonsContainer.offsetWidth}px)`; | |
| }); | |
| // 検索機能を追加 | |
| const searchInput = document.getElementById('historySearchInput'); | |
| searchInput.addEventListener('input', filterHistory); | |
| } | |
| } | |
| function deleteHistoryItem(index) { | |
| if (confirm('Are you sure you want to delete this history item?')) { | |
| let history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]'); | |
| history.splice(index, 1); | |
| localStorage.setItem('gemini_prompt_history', JSON.stringify(history)); | |
| updateHistoryList(); | |
| } | |
| } | |
| function loadHistoryItem(index) { | |
| const history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]'); | |
| const item = history[index]; | |
| document.getElementById('query').value = item.query; | |
| document.getElementById('promptEn').value = item.promptEn; | |
| document.getElementById('promptMyLanguage').value = item.promptMyLanguage; | |
| document.getElementById('danbooruTags').value = item.danbooruTags; | |
| saveToUserStorage(true); | |
| } | |
| function clearHistory() { | |
| if (confirm('Are you sure you want to delete all history items?')) { | |
| localStorage.removeItem('gemini_prompt_history'); | |
| updateHistoryList(); | |
| } | |
| } | |
| function createHistoryItem(item, index) { | |
| const li = document.createElement('li'); | |
| li.className = 'list-group-item d-flex justify-content-between align-items-center'; | |
| const titleSpan = document.createElement('span'); | |
| titleSpan.textContent = item.title || item.query.slice(0, 10); | |
| titleSpan.className = 'me-2'; | |
| li.appendChild(titleSpan); | |
| const buttonsContainer = document.createElement('div'); | |
| const editButton = document.createElement('button'); | |
| editButton.className = 'btn btn-sm btn-secondary me-2'; | |
| editButton.innerHTML = '<i class="fas fa-pencil-alt"></i>'; | |
| editButton.onclick = () => editHistoryItemTitle(index, titleSpan); | |
| const useButton = document.createElement('button'); | |
| useButton.className = 'btn btn-sm btn-primary me-2'; | |
| useButton.innerHTML = '<i class="fas fa-redo"></i>'; | |
| useButton.onclick = () => useHistoryItem(index); | |
| const deleteButton = document.createElement('button'); | |
| deleteButton.className = 'btn btn-sm btn-danger'; | |
| deleteButton.innerHTML = '<i class="fas fa-trash"></i>'; | |
| deleteButton.onclick = () => { | |
| if (confirm('Are you sure you want to delete this history item?')) { | |
| deleteHistoryItem(index); | |
| } | |
| }; | |
| buttonsContainer.appendChild(editButton); | |
| buttonsContainer.appendChild(useButton); | |
| buttonsContainer.appendChild(deleteButton); | |
| li.appendChild(buttonsContainer); | |
| return li; | |
| } | |
| function editHistoryItemTitle(index, titleDiv) { | |
| const history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]'); | |
| const item = history[index]; | |
| const currentTitle = item.title || item.query.slice(0, 10); | |
| const newTitle = prompt('New title:', currentTitle); | |
| if (newTitle !== null && newTitle.trim() !== '') { | |
| item.title = newTitle.trim(); | |
| history[index] = item; | |
| localStorage.setItem('gemini_prompt_history', JSON.stringify(history)); | |
| titleDiv.textContent = newTitle.trim(); | |
| } | |
| } | |
| function filterHistory() { | |
| const searchInput = document.getElementById('historySearchInput'); | |
| const searchTerm = searchInput.value.toLowerCase(); | |
| const historyItems = document.querySelectorAll('#historyList li[data-item]'); | |
| historyItems.forEach(item => { | |
| const itemData = JSON.parse(item.dataset.item); | |
| const searchableText = `${itemData.title} ${itemData.query} ${itemData.promptEn} ${itemData.promptMyLanguage} ${itemData.danbooruTags}`.toLowerCase(); | |
| if (searchableText.includes(searchTerm)) { | |
| item.classList.remove('d-none'); | |
| } else { | |
| item.classList.add('d-none'); | |
| } | |
| }); | |
| } | |