From 9bd66d1101ca765c3b73e5516e770b9534e23af1 Mon Sep 17 00:00:00 2001 From: danieljanata Date: Thu, 15 Jan 2026 07:32:40 +0100 Subject: [PATCH] Add files via upload --- __init__.py | 64 +++++++++-- js/prompting_system.js | 233 +++++++++++++++++++++++++++-------------- 2 files changed, 212 insertions(+), 85 deletions(-) diff --git a/__init__.py b/__init__.py index 93e1d60..ec4df95 100644 --- a/__init__.py +++ b/__init__.py @@ -211,6 +211,40 @@ class PromptDB: return True return False + def get_all_last_saved_ids(self): + """Get all recently saved prompt IDs (for thumbnail assignment)""" + if not hasattr(self, '_saver_last_ids'): + return [] + return list(self._saver_last_ids.values()) + + def update_prompt(self, pid, model=None, category=None, tags=None): + """Update prompt metadata""" + if pid not in self.data["prompts"]: + return False + + p = self.data["prompts"][pid] + + if model is not None: + p["model"] = model if model and model != "none" else None + + if category is not None: + p["category"] = category if category and category != "none" else None + + if tags is not None: + tag_list = [] + if tags: + for t in tags.split(","): + t = t.strip().lower() + if t: + tag_list.append(t) + if t not in self.data["tags"]: + self.data["tags"].append(t) + p["tags"] = tag_list + + p["updated_at"] = datetime.now().isoformat() + self._save() + return True + def get_categories(self): return self.data.get("categories", []) @@ -348,7 +382,7 @@ def create_thumbnail(image_path, size=64): def capture_last_output_image(): - """Find most recent image in output dir and create thumbnail for last saved prompt""" + """Find most recent image in output dir and create thumbnail for all recently saved prompts""" try: output_dir = folder_paths.get_output_directory() latest = None @@ -362,11 +396,16 @@ def capture_last_output_image(): latest_time = mt latest = fp - if latest and db.get_last_saved_id(): - thumb = create_thumbnail(latest, 64) - if thumb: - db.set_thumbnail(db.get_last_saved_id(), thumb) - return True + if latest: + # Get all recently saved prompt IDs + recent_ids = db.get_all_last_saved_ids() + if recent_ids: + thumb = create_thumbnail(latest, 64) + if thumb: + for pid in recent_ids: + db.set_thumbnail(pid, thumb) + print(f"[PS] Thumbnail assigned to {len(recent_ids)} prompts") + return True except Exception as e: print(f"[PS] Capture error: {e}") return False @@ -657,6 +696,19 @@ async def ps_rate(request): async def ps_delete(request): return web.json_response({"success": db.delete_prompt(request.match_info["pid"])}) +@routes.put("/ps/prompts/{pid}") +async def ps_update(request): + """Update prompt metadata (model, category, tags)""" + data = await request.json() + pid = request.match_info["pid"] + success = db.update_prompt( + pid, + model=data.get("model"), + category=data.get("category"), + tags=data.get("tags") + ) + return web.json_response({"success": success}) + @routes.get("/ps/export") async def ps_export(request): return web.json_response({"success": True, "data": db.export_data()}) diff --git a/js/prompting_system.js b/js/prompting_system.js index 4de921a..8e35bff 100644 --- a/js/prompting_system.js +++ b/js/prompting_system.js @@ -1,5 +1,5 @@ // ComfyUI-Prompting-System -// Sidebar with pagination, LoRA upload, per-node save tracking +// Sidebar with pagination, LoRA upload, per-node tracking, prompt editing import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; @@ -23,7 +23,7 @@ const psApi = async (path, opts = {}) => { const toast = (msg, type = "info") => { const colors = { success: "#a6e3a1", error: "#f38ba8", info: "#89b4fa" }; const el = document.createElement("div"); - el.style.cssText = `position:fixed;bottom:20px;left:50%;transform:translateX(-50%);padding:10px 20px;border-radius:8px;z-index:10001;background:${colors[type]};color:#1e1e2e;font-size:12px;font-weight:500;`; + el.style.cssText = `position:fixed;bottom:20px;left:50%;transform:translateX(-50%);padding:12px 24px;border-radius:8px;z-index:10001;background:${colors[type]};color:#1e1e2e;font-size:13px;font-weight:500;`; el.textContent = msg; document.body.appendChild(el); setTimeout(() => el.remove(), 2000); @@ -65,17 +65,17 @@ app.registerExtension({ const savedState = loadState(); const container = document.createElement('div'); - container.style.cssText = 'padding: 12px; color: #fff; font-family: Arial, sans-serif;'; + container.style.cssText = 'padding: 14px; color: #fff; font-family: Arial, sans-serif;'; // Header const header = document.createElement('h3'); header.textContent = '✨ Prompt Browser'; - header.style.cssText = 'margin: 0 0 12px 0; color: #cba6f7; font-size: 15px; border-bottom: 1px solid #333; padding-bottom: 8px;'; + header.style.cssText = 'margin: 0 0 14px 0; color: #cba6f7; font-size: 17px; border-bottom: 1px solid #444; padding-bottom: 10px;'; container.appendChild(header); // Stats const stats = document.createElement('div'); - stats.style.cssText = 'font-size: 11px; color: #888; margin-bottom: 10px;'; + stats.style.cssText = 'font-size: 13px; color: #888; margin-bottom: 12px;'; stats.textContent = 'Loading...'; container.appendChild(stats); @@ -84,14 +84,14 @@ app.registerExtension({ searchInput.type = 'text'; searchInput.placeholder = 'Search prompts...'; searchInput.value = savedState.search || ''; - searchInput.style.cssText = 'width: 100%; padding: 6px 8px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 5px; color: #fff; font-size: 11px; margin-bottom: 8px; box-sizing: border-box;'; + searchInput.style.cssText = 'width: 100%; padding: 10px 12px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; color: #fff; font-size: 13px; margin-bottom: 10px; box-sizing: border-box;'; container.appendChild(searchInput); // Filters const filtersRow = document.createElement('div'); - filtersRow.style.cssText = 'display: flex; gap: 4px; margin-bottom: 8px; flex-wrap: wrap;'; + filtersRow.style.cssText = 'display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap;'; - const selectStyle = 'flex: 1; min-width: 70px; padding: 4px; background: #333; border: 1px solid #444; border-radius: 4px; color: #fff; font-size: 10px;'; + const selectStyle = 'flex: 1; min-width: 80px; padding: 8px; background: #333; border: 1px solid #444; border-radius: 5px; color: #fff; font-size: 12px;'; const catSelect = document.createElement('select'); catSelect.style.cssText = selectStyle; @@ -107,23 +107,23 @@ app.registerExtension({ container.appendChild(filtersRow); - // Results list + // Results list - scrollable const resultsList = document.createElement('div'); - resultsList.style.cssText = 'max-height: 280px; overflow-y: auto;'; + resultsList.style.cssText = 'max-height: 400px; overflow-y: auto; margin-bottom: 10px;'; container.appendChild(resultsList); // Pagination const paginationRow = document.createElement('div'); - paginationRow.style.cssText = 'display: flex; align-items: center; justify-content: center; gap: 8px; margin: 8px 0; font-size: 11px;'; + paginationRow.style.cssText = 'display: flex; align-items: center; justify-content: center; gap: 10px; margin: 10px 0; font-size: 13px;'; container.appendChild(paginationRow); - // Copy + Refresh row + // Buttons row 1 const row1 = document.createElement('div'); - row1.style.cssText = 'display: flex; gap: 4px; margin-top: 10px; padding-top: 10px; border-top: 1px solid #333;'; + row1.style.cssText = 'display: flex; gap: 6px; margin-top: 12px; padding-top: 12px; border-top: 1px solid #444;'; const copyBtn = document.createElement('button'); copyBtn.textContent = '📋 Copy'; - copyBtn.style.cssText = 'flex: 1; padding: 6px; background: linear-gradient(135deg, #89b4fa, #74c7ec); border: none; border-radius: 5px; color: #1e1e2e; font-weight: bold; cursor: pointer; font-size: 11px;'; + copyBtn.style.cssText = 'flex: 1; padding: 10px; background: linear-gradient(135deg, #89b4fa, #74c7ec); border: none; border-radius: 6px; color: #1e1e2e; font-weight: bold; cursor: pointer; font-size: 13px;'; copyBtn.onclick = () => { if (selectedPromptText) { navigator.clipboard.writeText(selectedPromptText); @@ -136,17 +136,17 @@ app.registerExtension({ const refreshBtn = document.createElement('button'); refreshBtn.textContent = '🔄'; - refreshBtn.style.cssText = 'padding: 6px 10px; background: rgba(255,255,255,0.1); border: none; border-radius: 5px; color: #fff; cursor: pointer;'; + refreshBtn.style.cssText = 'padding: 10px 14px; background: rgba(255,255,255,0.1); border: none; border-radius: 6px; color: #fff; cursor: pointer; font-size: 14px;'; refreshBtn.onclick = () => loadData(); row1.appendChild(refreshBtn); container.appendChild(row1); - // WF + DB + Import row + // Buttons row 2 const row2 = document.createElement('div'); - row2.style.cssText = 'display: flex; gap: 4px; margin-top: 4px;'; + row2.style.cssText = 'display: flex; gap: 6px; margin-top: 6px;'; - const btnStyle2 = 'flex: 1; padding: 5px; background: rgba(255,255,255,0.1); border: none; border-radius: 4px; color: #fff; cursor: pointer; font-size: 10px;'; + const btnStyle2 = 'flex: 1; padding: 8px; background: rgba(255,255,255,0.1); border: none; border-radius: 5px; color: #fff; cursor: pointer; font-size: 12px;'; const wfBtn = document.createElement('button'); wfBtn.textContent = '📄 WF'; @@ -213,13 +213,13 @@ app.registerExtension({ container.appendChild(row2); - // Output ZIP + LoRA upload row + // Buttons row 3 const row3 = document.createElement('div'); - row3.style.cssText = 'display: flex; gap: 4px; margin-top: 4px;'; + row3.style.cssText = 'display: flex; gap: 6px; margin-top: 6px;'; const outputZipBtn = document.createElement('button'); outputZipBtn.textContent = '📦 Outputs ZIP'; - outputZipBtn.style.cssText = 'flex: 1; padding: 5px; background: rgba(249,226,175,0.3); border: none; border-radius: 4px; color: #1e1e2e; cursor: pointer; font-size: 10px; font-weight: 500;'; + outputZipBtn.style.cssText = 'flex: 1; padding: 8px; background: rgba(249,226,175,0.3); border: none; border-radius: 5px; color: #1e1e2e; cursor: pointer; font-size: 12px; font-weight: 500;'; outputZipBtn.onclick = async () => { toast('Creating ZIP...', 'info'); try { @@ -240,7 +240,7 @@ app.registerExtension({ const loraBtn = document.createElement('button'); loraBtn.textContent = '🎨 Upload LoRA'; - loraBtn.style.cssText = 'flex: 1; padding: 5px; background: rgba(203,166,247,0.3); border: none; border-radius: 4px; color: #1e1e2e; cursor: pointer; font-size: 10px; font-weight: 500;'; + loraBtn.style.cssText = 'flex: 1; padding: 8px; background: rgba(203,166,247,0.3); border: none; border-radius: 5px; color: #1e1e2e; cursor: pointer; font-size: 12px; font-weight: 500;'; loraBtn.onclick = () => { const input = document.createElement('input'); input.type = 'file'; @@ -249,7 +249,6 @@ app.registerExtension({ const file = e.target.files[0]; if (!file) return; - // Show progress loraBtn.textContent = '⏳ 0%'; loraBtn.disabled = true; @@ -303,15 +302,15 @@ app.registerExtension({ // Settings section const settingsSection = document.createElement('div'); - settingsSection.style.cssText = 'margin-top: 10px; padding-top: 10px; border-top: 1px solid #333;'; + settingsSection.style.cssText = 'margin-top: 14px; padding-top: 14px; border-top: 1px solid #444;'; // Per page setting const perPageRow = document.createElement('div'); - perPageRow.style.cssText = 'display: flex; align-items: center; gap: 6px; margin-bottom: 8px;'; - perPageRow.innerHTML = 'Per page:'; + perPageRow.style.cssText = 'display: flex; align-items: center; gap: 8px; margin-bottom: 12px;'; + perPageRow.innerHTML = 'Per page:'; const perPageSelect = document.createElement('select'); - perPageSelect.style.cssText = 'padding: 3px; background: #333; border: 1px solid #444; border-radius: 3px; color: #fff; font-size: 10px;'; + perPageSelect.style.cssText = 'padding: 6px; background: #333; border: 1px solid #444; border-radius: 4px; color: #fff; font-size: 12px;'; [4, 8, 12, 16, 24].forEach(n => { const opt = document.createElement('option'); opt.value = n; @@ -325,26 +324,26 @@ app.registerExtension({ // Management header const mgmtHeader = document.createElement('div'); mgmtHeader.textContent = '⚙️ Categories & Models'; - mgmtHeader.style.cssText = 'font-size: 11px; color: #89b4fa; margin-bottom: 8px; font-weight: bold;'; + mgmtHeader.style.cssText = 'font-size: 13px; color: #89b4fa; margin-bottom: 10px; font-weight: bold;'; settingsSection.appendChild(mgmtHeader); // Categories const catMgmt = document.createElement('div'); - catMgmt.style.cssText = 'margin-bottom: 8px;'; - catMgmt.innerHTML = '
Categories:
'; + catMgmt.style.cssText = 'margin-bottom: 10px;'; + catMgmt.innerHTML = '
Categories:
'; const catChips = document.createElement('div'); - catChips.style.cssText = 'display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: 4px;'; + catChips.style.cssText = 'display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px;'; catMgmt.appendChild(catChips); const catAddRow = document.createElement('div'); - catAddRow.style.cssText = 'display: flex; gap: 3px;'; + catAddRow.style.cssText = 'display: flex; gap: 4px;'; const catInput = document.createElement('input'); catInput.placeholder = 'New category...'; - catInput.style.cssText = 'flex: 1; padding: 3px 6px; background: #333; border: 1px solid #444; border-radius: 3px; color: #fff; font-size: 10px;'; + catInput.style.cssText = 'flex: 1; padding: 6px 8px; background: #333; border: 1px solid #444; border-radius: 4px; color: #fff; font-size: 12px;'; catAddRow.appendChild(catInput); const catAddBtn = document.createElement('button'); catAddBtn.textContent = '+'; - catAddBtn.style.cssText = 'padding: 3px 8px; background: #89b4fa; border: none; border-radius: 3px; color: #1e1e2e; font-weight: bold; cursor: pointer;'; + catAddBtn.style.cssText = 'padding: 6px 12px; background: #89b4fa; border: none; border-radius: 4px; color: #1e1e2e; font-weight: bold; cursor: pointer;'; catAddBtn.onclick = async () => { if (catInput.value.trim()) { await psApi('/categories', { method: 'POST', body: JSON.stringify({ name: catInput.value.trim() }) }); @@ -358,20 +357,20 @@ app.registerExtension({ // Models const modelMgmt = document.createElement('div'); - modelMgmt.innerHTML = '
Models:
'; + modelMgmt.innerHTML = '
Models:
'; const modelChips = document.createElement('div'); - modelChips.style.cssText = 'display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: 4px;'; + modelChips.style.cssText = 'display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px;'; modelMgmt.appendChild(modelChips); const modelAddRow = document.createElement('div'); - modelAddRow.style.cssText = 'display: flex; gap: 3px;'; + modelAddRow.style.cssText = 'display: flex; gap: 4px;'; const modelInput = document.createElement('input'); modelInput.placeholder = 'New model...'; - modelInput.style.cssText = 'flex: 1; padding: 3px 6px; background: #333; border: 1px solid #444; border-radius: 3px; color: #fff; font-size: 10px;'; + modelInput.style.cssText = 'flex: 1; padding: 6px 8px; background: #333; border: 1px solid #444; border-radius: 4px; color: #fff; font-size: 12px;'; modelAddRow.appendChild(modelInput); const modelAddBtn = document.createElement('button'); modelAddBtn.textContent = '+'; - modelAddBtn.style.cssText = 'padding: 3px 8px; background: #89b4fa; border: none; border-radius: 3px; color: #1e1e2e; font-weight: bold; cursor: pointer;'; + modelAddBtn.style.cssText = 'padding: 6px 12px; background: #89b4fa; border: none; border-radius: 4px; color: #1e1e2e; font-weight: bold; cursor: pointer;'; modelAddBtn.onclick = async () => { if (modelInput.value.trim()) { await psApi('/models', { method: 'POST', body: JSON.stringify({ name: modelInput.value.trim() }) }); @@ -393,25 +392,81 @@ app.registerExtension({ let currentTag = savedState.tag || 'All'; let currentPage = 1; let perPage = savedState.perPage || 8; - let totalPrompts = 0; let selectedPromptId = null; let selectedPromptText = ''; + let allCategories = []; + let allModels = []; const persistState = () => { saveState({ search: currentSearch, category: currentCategory, model: currentModel, tag: currentTag, perPage }); }; + // Show edit modal + const showEditModal = (prompt) => { + const overlay = document.createElement('div'); + overlay.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 10000; display: flex; align-items: center; justify-content: center;'; + + const modal = document.createElement('div'); + modal.style.cssText = 'background: #2a2a3a; border-radius: 12px; padding: 20px; width: 350px; max-width: 90%;'; + + modal.innerHTML = ` +

Edit Prompt

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ `; + + overlay.appendChild(modal); + document.body.appendChild(overlay); + + overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); }; + modal.querySelector('#edit-cancel').onclick = () => overlay.remove(); + modal.querySelector('#edit-save').onclick = async () => { + const category = modal.querySelector('#edit-category').value; + const model = modal.querySelector('#edit-model').value; + const tags = modal.querySelector('#edit-tags').value; + + await psApi(`/prompts/${prompt.id}`, { + method: 'PUT', + body: JSON.stringify({ category, model, tags }) + }); + + overlay.remove(); + toast('Saved!', 'success'); + loadData(); + }; + }; + // Load data const loadData = async () => { const statsRes = await psApi('/stats'); if (statsRes.success) { stats.textContent = `📝 ${statsRes.stats.total} | ⭐ ${statsRes.stats.rated} | 🖼 ${statsRes.stats.with_thumbnail}`; - totalPrompts = statsRes.stats.total; } // Categories const catRes = await psApi('/categories'); if (catRes.success) { + allCategories = catRes.categories; catSelect.innerHTML = '' + catRes.categories.map(c => ``).join(''); catSelect.value = currentCategory; @@ -419,7 +474,7 @@ app.registerExtension({ catChips.innerHTML = ''; catRes.categories.forEach(c => { const chip = document.createElement('span'); - chip.style.cssText = 'display: inline-flex; align-items: center; gap: 3px; padding: 2px 5px; background: rgba(203,166,247,0.15); color: #cba6f7; border-radius: 3px; font-size: 9px;'; + chip.style.cssText = 'display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; background: rgba(203,166,247,0.15); color: #cba6f7; border-radius: 4px; font-size: 11px;'; chip.innerHTML = `${c} ×`; chip.querySelector('span').onclick = async (e) => { e.stopPropagation(); @@ -433,6 +488,7 @@ app.registerExtension({ // Models const modelRes = await psApi('/models'); if (modelRes.success) { + allModels = modelRes.models; modelSelect.innerHTML = '' + modelRes.models.map(m => ``).join(''); modelSelect.value = currentModel; @@ -440,7 +496,7 @@ app.registerExtension({ modelChips.innerHTML = ''; modelRes.models.forEach(m => { const chip = document.createElement('span'); - chip.style.cssText = 'display: inline-flex; align-items: center; gap: 3px; padding: 2px 5px; background: rgba(250,179,135,0.2); color: #fab387; border-radius: 3px; font-size: 9px;'; + chip.style.cssText = 'display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; background: rgba(250,179,135,0.2); color: #fab387; border-radius: 4px; font-size: 11px;'; chip.innerHTML = `${m} ×`; chip.querySelector('span').onclick = async (e) => { e.stopPropagation(); @@ -459,19 +515,19 @@ app.registerExtension({ tagSelect.value = currentTag; } - // Prompts with pagination + // Prompts const params = new URLSearchParams(); if (currentSearch) params.append('search', currentSearch); if (currentCategory !== 'All') params.append('category', currentCategory); if (currentModel !== 'All') params.append('model', currentModel); if (currentTag !== 'All') params.append('tag', currentTag); - params.append('limit', '500'); // Get all for client-side pagination + params.append('limit', '500'); const promptsRes = await psApi(`/prompts?${params}`); resultsList.innerHTML = ''; if (!promptsRes.success || !promptsRes.prompts?.length) { - resultsList.innerHTML = '
No prompts found
'; + resultsList.innerHTML = '
No prompts found
'; paginationRow.innerHTML = ''; return; } @@ -483,10 +539,10 @@ app.registerExtension({ const startIdx = (currentPage - 1) * perPage; const pagePrompts = allPrompts.slice(startIdx, startIdx + perPage); - // Render prompts + // Render prompts - FULL TEXT, variable height pagePrompts.forEach(p => { const card = document.createElement('div'); - card.style.cssText = `display: flex; gap: 8px; background: rgba(255,255,255,0.03); border: 1px solid ${selectedPromptId === p.id ? '#cba6f7' : 'rgba(255,255,255,0.08)'}; border-radius: 6px; padding: 8px; margin-bottom: 6px; cursor: pointer;`; + card.style.cssText = `display: flex; gap: 10px; background: rgba(255,255,255,0.03); border: 2px solid ${selectedPromptId === p.id ? '#cba6f7' : 'rgba(255,255,255,0.08)'}; border-radius: 8px; padding: 12px; margin-bottom: 10px; cursor: pointer; transition: border-color 0.15s;`; card.onclick = () => { selectedPromptId = p.id; @@ -501,39 +557,59 @@ app.registerExtension({ // Tags row const topRow = document.createElement('div'); - topRow.style.cssText = 'display: flex; gap: 3px; margin-bottom: 4px; flex-wrap: wrap;'; + topRow.style.cssText = 'display: flex; gap: 4px; margin-bottom: 8px; flex-wrap: wrap; align-items: center;'; if (p.model) { const tag = document.createElement('span'); - tag.style.cssText = 'background: rgba(250,179,135,0.2); color: #fab387; padding: 1px 4px; border-radius: 3px; font-size: 8px;'; + tag.style.cssText = 'background: rgba(250,179,135,0.2); color: #fab387; padding: 2px 6px; border-radius: 4px; font-size: 10px;'; tag.textContent = p.model; topRow.appendChild(tag); } if (p.category) { const tag = document.createElement('span'); - tag.style.cssText = 'background: rgba(203,166,247,0.15); color: #cba6f7; padding: 1px 4px; border-radius: 3px; font-size: 8px;'; + tag.style.cssText = 'background: rgba(203,166,247,0.15); color: #cba6f7; padding: 2px 6px; border-radius: 4px; font-size: 10px;'; tag.textContent = p.category; topRow.appendChild(tag); } + if (p.tags && p.tags.length) { + p.tags.forEach(t => { + const tag = document.createElement('span'); + tag.style.cssText = 'background: rgba(137,180,250,0.15); color: #89b4fa; padding: 2px 6px; border-radius: 4px; font-size: 10px;'; + tag.textContent = t; + topRow.appendChild(tag); + }); + } + + // Edit button + const editBtn = document.createElement('span'); + editBtn.textContent = '✏️'; + editBtn.title = 'Edit'; + editBtn.style.cssText = 'cursor: pointer; margin-left: auto; font-size: 12px; opacity: 0.6;'; + editBtn.onclick = (e) => { + e.stopPropagation(); + showEditModal(p); + }; + topRow.appendChild(editBtn); + content.appendChild(topRow); - // Text + // FULL TEXT - no truncation const textDiv = document.createElement('div'); - textDiv.style.cssText = 'font-size: 10px; color: #bac2de; max-height: 32px; overflow: hidden; margin-bottom: 4px; line-height: 1.3;'; - textDiv.textContent = p.text.substring(0, 100) + (p.text.length > 100 ? '...' : ''); + textDiv.style.cssText = 'font-size: 13px; color: #cdd6f4; line-height: 1.5; margin-bottom: 8px; word-break: break-word;'; + textDiv.textContent = p.text; content.appendChild(textDiv); - // Bottom row + // Bottom row - stars + delete const bottomRow = document.createElement('div'); - bottomRow.style.cssText = 'display: flex; align-items: center; gap: 4px;'; + bottomRow.style.cssText = 'display: flex; align-items: center; gap: 6px;'; // Stars const starsDiv = document.createElement('span'); - starsDiv.style.cssText = 'color: #f9e2af; font-size: 10px; margin-left: auto;'; + starsDiv.style.cssText = 'color: #f9e2af; font-size: 14px;'; for (let i = 1; i <= 5; i++) { const star = document.createElement('span'); star.textContent = '★'; - star.style.cssText = `cursor: pointer; opacity: ${i <= (p.rating || 0) ? 1 : 0.3};`; + star.style.cssText = `cursor: pointer; opacity: ${i <= (p.rating || 0) ? 1 : 0.3}; transition: opacity 0.1s;`; star.onclick = async (e) => { e.stopPropagation(); await psApi(`/prompts/${p.id}/rate`, { method: 'POST', body: JSON.stringify({ rating: i === p.rating ? 0 : i }) }); @@ -543,13 +619,18 @@ app.registerExtension({ } bottomRow.appendChild(starsDiv); + // Spacer + const spacer = document.createElement('span'); + spacer.style.cssText = 'flex: 1;'; + bottomRow.appendChild(spacer); + // Delete const delBtn = document.createElement('span'); - delBtn.textContent = '🗑'; - delBtn.style.cssText = 'cursor: pointer; opacity: 0.5; font-size: 10px;'; + delBtn.textContent = '🗑️'; + delBtn.style.cssText = 'cursor: pointer; opacity: 0.5; font-size: 14px;'; delBtn.onclick = async (e) => { e.stopPropagation(); - if (confirm('Delete?')) { + if (confirm('Delete this prompt?')) { await psApi(`/prompts/${p.id}`, { method: 'DELETE' }); loadData(); } @@ -560,43 +641,38 @@ app.registerExtension({ card.appendChild(content); // Thumbnail - const thumbDiv = document.createElement('div'); - thumbDiv.style.cssText = 'width: 50px; height: 50px; background: rgba(255,255,255,0.05); border-radius: 4px; flex-shrink: 0; overflow: hidden; display: flex; align-items: center; justify-content: center;'; - if (p.thumbnail) { + const thumbDiv = document.createElement('div'); + thumbDiv.style.cssText = 'width: 70px; height: 70px; background: rgba(255,255,255,0.05); border-radius: 6px; flex-shrink: 0; overflow: hidden;'; const img = document.createElement('img'); img.src = `data:image/jpeg;base64,${p.thumbnail}`; img.style.cssText = 'width: 100%; height: 100%; object-fit: cover;'; thumbDiv.appendChild(img); - } else { - thumbDiv.style.color = '#45475a'; - thumbDiv.style.fontSize = '16px'; - thumbDiv.textContent = '🖼'; + card.appendChild(thumbDiv); } - card.appendChild(thumbDiv); resultsList.appendChild(card); }); - // Pagination controls + // Pagination paginationRow.innerHTML = ''; if (totalPages > 1) { const prevBtn = document.createElement('button'); - prevBtn.textContent = '◀'; + prevBtn.textContent = '◀ Prev'; prevBtn.disabled = currentPage === 1; - prevBtn.style.cssText = 'padding: 3px 8px; background: rgba(255,255,255,0.1); border: none; border-radius: 3px; color: #fff; cursor: pointer; font-size: 10px;'; + prevBtn.style.cssText = 'padding: 6px 12px; background: rgba(255,255,255,0.1); border: none; border-radius: 4px; color: #fff; cursor: pointer; font-size: 12px;'; prevBtn.onclick = () => { currentPage--; loadData(); }; paginationRow.appendChild(prevBtn); const pageInfo = document.createElement('span'); pageInfo.textContent = `${currentPage} / ${totalPages}`; - pageInfo.style.cssText = 'color: #888;'; + pageInfo.style.cssText = 'color: #888; font-size: 13px;'; paginationRow.appendChild(pageInfo); const nextBtn = document.createElement('button'); - nextBtn.textContent = '▶'; + nextBtn.textContent = 'Next ▶'; nextBtn.disabled = currentPage === totalPages; - nextBtn.style.cssText = 'padding: 3px 8px; background: rgba(255,255,255,0.1); border: none; border-radius: 3px; color: #fff; cursor: pointer; font-size: 10px;'; + nextBtn.style.cssText = 'padding: 6px 12px; background: rgba(255,255,255,0.1); border: none; border-radius: 4px; color: #fff; cursor: pointer; font-size: 12px;'; nextBtn.onclick = () => { currentPage++; loadData(); }; paginationRow.appendChild(nextBtn); } @@ -713,7 +789,6 @@ app.registerExtension({ const textWidget = this.widgets?.find(w => w.name === 'text'); const nodeId = this.id; - // Reset for THIS specific node only const resetForNew = async () => { await psApi('/reset-last-saved', { method: 'POST', @@ -727,14 +802,14 @@ app.registerExtension({ textWidget.value = text; await resetForNew(); app.graph.setDirtyCanvas(true); - toast('Pasted! NEW', 'info'); + toast('Pasted! Next = NEW', 'info'); } } else if (index === 1) { // Clear if (textWidget) { textWidget.value = ''; await resetForNew(); app.graph.setDirtyCanvas(true); - toast('Cleared! NEW', 'info'); + toast('Cleared! Next = NEW', 'info'); } } else if (index === 2) { // Mark NEW await resetForNew(); @@ -776,7 +851,7 @@ app.registerExtension({ const dlBtn = document.createElement('button'); dlBtn.textContent = '📥 Download Workflow JSON'; - dlBtn.style.cssText = 'width: 100%; padding: 6px; background: linear-gradient(135deg, #89b4fa, #74c7ec); border: none; border-radius: 4px; color: #1e1e2e; font-weight: 600; cursor: pointer; font-size: 11px;'; + dlBtn.style.cssText = 'width: 100%; padding: 8px; background: linear-gradient(135deg, #89b4fa, #74c7ec); border: none; border-radius: 4px; color: #1e1e2e; font-weight: 600; cursor: pointer; font-size: 12px;'; dlBtn.onclick = () => toast('Check prompts output', 'info'); btnContainer.appendChild(dlBtn);