Discusión

Organizador de Prompts - Constructor de Aplicaciones Web de Archivo Único

De Wikiprompt, la enciclopedia libre de prompts

Paul Couvert
Contribuido porPaul CouvertXFuente

21 abr 2025

Organizador de Prompts - Constructor de Aplicaciones Web de Archivo Único Un prompt detallado para generar una aplicación de organizador de prompts basada en navegador con persistencia en localStorage, búsqueda, filtrado y funciones de ordenamiento.

Contenido del PromptGuardar

🌐
Here is the single-file HTML code for a "Prompt Organizer" web application. It allows you to save, edit, delete, and categorize AI prompts directly in your browser, with all data stored locally. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Prompt Organizer · local-first</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } body { background: #f8fafc; color: #1e293b; line-height: 1.5; padding: 1.5rem; min-height: 100vh; display: flex; justify-content: center; } .app-wrapper { max-width: 1200px; width: 100%; background: white; border-radius: 24px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.05); padding: 2rem 1.8rem; } header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; margin-bottom: 2rem; gap: 1rem; } h1 { font-size: 1.9rem; font-weight: 600; letter-spacing: -0.02em; background: linear-gradient(145deg, #0f172a, #334155); -webkit-background-clip: text; background-clip: text; color: transparent; } .subhead { color: #64748b; font-size: 0.95rem; margin-top: 0.2rem; } .toolbar { display: flex; flex-wrap: wrap; gap: 0.8rem; align-items: center; background: #f1f5f9; padding: 0.8rem 1rem; border-radius: 40px; margin-bottom: 1.8rem; } .search-box { flex: 2 1 240px; display: flex; align-items: center; background: white; border-radius: 40px; padding: 0.3rem 0.8rem; border: 1px solid #e2e8f0; } .search-box input { border: none; outline: none; background: transparent; padding: 0.5rem 0.4rem; font-size: 0.95rem; width: 100%; } .filter-tags { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; } .tag-filter-btn { background: white; border: 1px solid #cbd5e1; padding: 0.3rem 0.9rem; border-radius: 30px; font-size: 0.8rem; font-weight: 500; cursor: pointer; transition: all 0.15s; color: #334155; } .tag-filter-btn.active { background: #0f172a; color: white; border-color: #0f172a; } .sort-select { background: white; border: 1px solid #cbd5e1; border-radius: 30px; padding: 0.4rem 1rem; font-size: 0.85rem; cursor: pointer; color: #1e293b; } .btn { background: white; border: 1px solid #cbd5e1; padding: 0.5rem 1.2rem; border-radius: 40px; font-weight: 500; font-size: 0.9rem; cursor: pointer; transition: 0.15s; display: inline-flex; align-items: center; gap: 0.3rem; } .btn-primary { background: #0f172a; color: white; border: none; box-shadow: 0 4px 10px rgba(15, 23, 42, 0.15); } .btn-primary:hover { background: #1e293b; } .btn-outline:hover { background: #f1f5f9; } .prompt-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem; margin-top: 1.2rem; } .prompt-card { background: white; border-radius: 18px; border: 1px solid #e2e8f0; padding: 1.2rem 1.2rem 1rem; transition: 0.2s; display: flex; flex-direction: column; box-shadow: 0 2px 8px rgba(0,0,0,0.02); } .prompt-card:hover { box-shadow: 0 12px 28px rgba(0,0,0,0.06); border-color: #cbd5e1; } .card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.6rem; } .card-title { font-weight: 600; font-size: 1.1rem; color: #0f172a; word-break: break-word; } .card-actions { display: flex; gap: 0.3rem; flex-shrink: 0; } .icon-btn { background: transparent; border: none; font-size: 1.1rem; cursor: pointer; color: #64748b; padding: 0.2rem 0.4rem; border-radius: 8px; transition: 0.1s; } .icon-btn:hover { background: #f1f5f9; color: #0f172a; } .card-desc { font-size: 0.9rem; color: #475569; margin-bottom: 0.8rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .prompt-text-preview { background: #f8fafc; border-radius: 12px; padding: 0.7rem 0.9rem; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.8rem; color: #1e293b; white-space: pre-wrap; word-break: break-word; margin-bottom: 1rem; border-left: 3px solid #94a3b8; max-height: 100px; overflow-y: auto; } .card-meta { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; font-size: 0.75rem; color: #64748b; border-top: 1px solid #eef2f6; padding-top: 0.6rem; margin-top: auto; } .tag-list { display: flex; flex-wrap: wrap; gap: 0.3rem; } .tag-badge { background: #eef2ff; color: #4338ca; padding: 0.2rem 0.7rem; border-radius: 20px; font-size: 0.7rem; font-weight: 500; } .empty-state { text-align: center; padding: 3rem 1rem; color: #94a3b8; font-size: 1rem; } /* modal */ .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(15, 23, 42, 0.4); backdrop-filter: blur(4px); justify-content: center; align-items: center; z-index: 1000; } .modal.open { display: flex; } .modal-content { background: white; width: 90%; max-width: 600px; border-radius: 28px; padding: 2rem 1.8rem; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25); max-height: 90vh; overflow-y: auto; } .modal-content h2 { font-size: 1.5rem; margin-bottom: 1.2rem; color: #0f172a; } .form-group { margin-bottom: 1rem; } .form-group label { display: block; font-size: 0.85rem; font-weight: 500; margin-bottom: 0.3rem; color: #334155; } .form-group input, .form-group textarea { width: 100%; padding: 0.7rem 1rem; border: 1px solid #cbd5e1; border-radius: 16px; font-size: 0.9rem; background: white; transition: 0.15s; } .form-group input:focus, .form-group textarea:focus { outline: none; border-color: #0f172a; box-shadow: 0 0 0 3px rgba(15,23,42,0.08); } .form-group textarea { min-height: 100px; resize: vertical; } .modal-actions { display: flex; justify-content: flex-end; gap: 0.8rem; margin-top: 1.5rem; } .tooltip { position: relative; display: inline-block; } .tooltip .tooltip-text { visibility: hidden; width: 160px; background-color: #1e293b; color: #fff; text-align: center; padding: 5px 8px; border-radius: 8px; font-size: 0.7rem; position: absolute; z-index: 1; bottom: 130%; left: 50%; margin-left: -80px; opacity: 0; transition: opacity 0.2s; } .tooltip:hover .tooltip-text { visibility: visible; opacity: 1; } @media (max-width: 640px) { .app-wrapper { padding: 1.2rem; } .toolbar { flex-direction: column; align-items: stretch; } .search-box { width: 100%; } } </style> </head> <body> <div class="app-wrapper"> <header> <div> <h1>📋 Prompt Organizer</h1> <div class="subhead">Save, edit & categorize AI prompts - stored locally</div> </div> <button class="btn btn-primary" id="newPromptBtn">+ New prompt</button> </header> <!-- Toolbar: search, tag filter, sort --> <div class="toolbar"> <div class="search-box"> <span style="margin-right: 6px; opacity: 0.6;">🔍</span> <input type="text" id="searchInput" placeholder="Search by title, description, prompt text..." autocomplete="off"> </div> <div class="filter-tags" id="filterTagsContainer"></div> <select id="sortSelect" class="sort-select" title="Sort by date"> <option value="created_desc">Created (newest)</option> <option value="created_asc">Created (oldest)</option> <option value="modified_desc">Modified (newest)</option> <option value="modified_asc">Modified (oldest)</option> </select> </div> <!-- Prompt grid --> <div id="promptGrid" class="prompt-grid"></div> <div id="emptyState" class="empty-state" style="display: none;">✨ No prompts match. Try adjusting filters.</div> </div> <!-- Modal for create/edit --> <div id="modal" class="modal"> <div class="modal-content"> <h2 id="modalTitle">New prompt</h2> <div class="form-group"> <label>Title *</label> <input type="text" id="titleInput" placeholder="e.g. Summarize meeting notes"> </div> <div class="form-group"> <label>Description</label> <input type="text" id="descInput" placeholder="Short description (optional)"> </div> <div class="form-group"> <label>Tags (comma separated)</label> <input type="text" id="tagsInput" placeholder="writing, coding, creative"> </div> <div class="form-group"> <label>Prompt text *</label> <textarea id="promptTextInput" placeholder="Paste your full prompt here..."></textarea> </div> <div class="modal-actions"> <button class="btn btn-outline" id="cancelModalBtn">Cancel</button> <button class="btn btn-primary" id="savePromptBtn">💾 Save prompt</button> </div> </div> </div> <script> (function() { // ---------- storage & seed ---------- const STORAGE_KEY = 'promptOrganizer.prompts.v1'; let prompts = []; let currentFilterTag = 'all'; let editingId = null; // null = create mode // ----- sample data (first load) ----- const seedPrompts = [ { id: 'p1', title: 'Code review assistant', description: 'Ask for a thorough code review with security focus.', tags: ['coding', 'review'], promptText: 'Act as a senior developer. Review the following code for bugs, security issues, and performance. Suggest improvements.\n\n[code snippet]', createdAt: Date.now() - 86400000 * 2, updatedAt: Date.now() - 3600000 }, { id: 'p2', title: 'Blog outline generator', description: 'Generate a structured outline for a tech blog post.', tags: ['writing', 'creative'], promptText: 'Create a detailed outline for a blog post about "local-first software". Include introduction, 5 main sections, and conclusion. Use engaging subheadings.', createdAt: Date.now() - 86400000 * 5, updatedAt: Date.now() - 86400000 }, { id: 'p3', title: 'SQL query explainer', description: 'Explain complex SQL queries in simple terms.', tags: ['data', 'learning'], promptText: 'Explain the following SQL query step by step, what it does, and potential edge cases. Use simple language.\n\nSELECT * FROM orders WHERE total > 100 ORDER BY created_at DESC;', createdAt: Date.now() - 86400000 * 1, updatedAt: Date.now() - 7200000 }, { id: 'p4', title: 'Meeting action items', description: 'Extract action items from meeting transcript.', tags: ['productivity', 'business'], promptText: 'From the transcript below, list all action items, owners, and deadlines. Format as a table.\n\n[transcript]', createdAt: Date.now() - 86400000 * 3, updatedAt: Date.now() - 86400000 * 3 } ]; // load or seed function loadPrompts() { try { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { prompts = JSON.parse(stored); if (!Array.isArray(prompts) || prompts.length === 0) { prompts = [...seedPrompts]; persist(); } } else { prompts = [...seedPrompts]; persist(); } } catch (e) { prompts = [...seedPrompts]; persist(); } } function persist() { localStorage.setItem(STORAGE_KEY, JSON.stringify(prompts)); } // ---------- helpers ---------- function formatDate(ts) { if (!ts) return ''; const d = new Date(ts); return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' ' + d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); } // get unique tags from prompts function getAllTags() { const tagSet = new Set(['all']); prompts.forEach(p => { (p.tags || []).forEach(t => tagSet.add(t)); }); return Array.from(tagSet).sort(); } // ---------- rendering ---------- function renderFilterTags() { const container = document.getElementById('filterTagsContainer'); const tags = getAllTags(); container.innerHTML = ''; tags.forEach(tag => { const btn = document.createElement('button'); btn.className = 'tag-filter-btn' + (currentFilterTag === tag ? ' active' : ''); btn.textContent = tag; btn.dataset.tag = tag; btn.addEventListener('click', () => { currentFilterTag = tag; renderFilterTags(); renderPrompts(); }); container.appendChild(btn); }); } function getFilteredSortedPrompts() { const searchTerm = document.getElementById('searchInput').value.trim().toLowerCase(); const sortBy = document.getElementById('sortSelect').value; let filtered = prompts.filter(p => { // tag filter if (currentFilterTag !== 'all' && !(p.tags || []).includes(currentFilterTag)) return false; // search if (searchTerm) { const haystack = `${p.title} ${p.description || ''} ${p.promptText} ${(p.tags || []).join(' ')}`.toLowerCase(); if (!haystack.includes(searchTerm)) return false; } return true; }); // sort filtered.sort((a, b) => { const dateA = (sortBy.includes('created')) ? a.createdAt : a.updatedAt; const dateB = (sortBy.includes('created')) ? b.createdAt : b.updatedAt; if (sortBy.endsWith('asc')) return dateA - dateB; else return dateB - dateA; }); return filtered; } function renderPrompts() { const grid = document.getElementById('promptGrid'); const empty = document.getElementById('emptyState'); const filtered = getFilteredSortedPrompts(); if (filtered.length === 0) { grid.innerHTML = ''; empty.style.display = 'block'; return; } empty.style.display = 'none'; grid.innerHTML = ''; filtered.forEach(prompt => { const card = document.createElement('div'); card.className = 'prompt-card'; const tagsHtml = (prompt.tags || []).map(t => `<span class="tag-badge">${escapeHtml(t)}</span>`).join(''); card.innerHTML = ` <div class="card-header"> <div class="card-title">${escapeHtml(prompt.title)}</div> <div class="card-actions"> <button class="icon-btn edit-btn tooltip" data-id="${prompt.id}" title="Edit prompt">✏️</button> <button class="icon-btn delete-btn tooltip" data-id="${prompt.id}" title="Delete prompt">🗑️</button> </div> </div> <div class="card-desc">${escapeHtml(prompt.description || 'No description')}</div> <div class="prompt-text-preview">${escapeHtml(prompt.promptText.length > 180 ? prompt.promptText.slice(0, 180) + '…' : prompt.promptText)}</div> <div class="card-meta"> <div class="tag-list">${tagsHtml || '<span style="opacity:0.5">no tags</span>'}</div> <span title="Created: ${formatDate(prompt.createdAt)} · Modified: ${formatDate(prompt.updatedAt)}"> 🕒 ${formatDate(prompt.updatedAt)} </span> </div> `; // attach events card.querySelector('.edit-btn').addEventListener('click', (e) => { e.stopPropagation(); openModal(prompt.id); }); card.querySelector('.delete-btn').addEventListener('click', (e) => { e.stopPropagation(); if (confirm(`Delete prompt "${prompt.title}"?`)) { prompts = prompts.filter(p => p.id !== prompt.id); persist(); renderFilterTags(); renderPrompts(); } }); grid.appendChild(card); }); } function escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>"']/g, (m) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }[m])); } // ---------- modal logic ---------- const modal = document.getElementById('modal'); const modalTitle = document.getElementById('modalTitle'); const titleInput = document.getElementById('titleInput'); const descInput = document.getElementById('descInput'); const tagsInput = document.getElementById('tagsInput'); const promptTextInput = document.getElementById('promptTextInput'); function openModal(id = null) { editingId = id; modal.classList.add('open'); if (id) { const p = prompts.find(x => x.id === id); if (!p) return; modalTitle.textContent = 'Edit prompt'; titleInput.value = p.title || ''; descInput.value = p.description || ''; tagsInput.value = (p.tags || []).join(', '); promptTextInput.value = p.promptText || ''; } else { modalTitle.textContent = 'New prompt'; titleInput.value = ''; descInput.value = ''; tagsInput.value = ''; promptTextInput.value = ''; } setTimeout(() => titleInput.focus(), 50); } function closeModal() { modal.classList.remove('open'); editingId = null; } function saveModal() { const title = titleInput.value.trim(); const desc = descInput.value.trim(); const tags = tagsInput.value.split(',').map(t => t.trim().toLowerCase()).filter(t => t !== ''); const promptText = promptTextInput.value.trim(); if (!title || !promptText) { alert('Title and prompt text are required.'); return; } if (editingId) { const idx = prompts.findIndex(p => p.id === editingId); if (idx !== -1) { prompts[idx] = { ...prompts[idx], title, description: desc, tags, promptText, updatedAt: Date.now() }; } } else { const newId = 'p_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6); prompts.push({ id: newId, title, description: desc, tags, promptText, createdAt: Date.now(), updatedAt: Date.now() }); } persist(); renderFilterTags(); renderPrompts(); closeModal(); } // ---------- event listeners ---------- document.getElementById('newPromptBtn').addEventListener('click', () => openModal()); document.getElementById('cancelModalBtn').addEventListener('click', closeModal); document.getElementById('savePromptBtn').addEventListener('click', saveModal); modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); }); document.getElementById('searchInput').addEventListener('input', renderPrompts); document.getElementById('sortSelect').addEventListener('change', renderPrompts); // keyboard: escape closes modal document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modal.classList.contains('open')) closeModal(); }); // ---------- init ---------- loadPrompts(); renderFilterTags(); renderPrompts(); })(); </script> </body> </html> ``` ### Core Features This tool is built around a simple, card-based workflow. You can quickly add new prompts, find existing ones, and keep everything organized with tags. - **Prompt Management**: The main grid displays each prompt as a card. Use the "New prompt" button to add entries, and the edit (✏️) or delete (🗑️) icons on each card to modify or remove them. - **Organization & Search**: The toolbar provides a keyword search box, clickable tag filters for quick categorization, and a dropdown to sort prompts by their creation or modification date. - **Data Persistence**: All your prompts are saved automatically to your browser's local storage. The app is pre-loaded with a few example prompts so you can see how it works immediately.

Iniciá sesión para ver el prompt completo

Continuar con:

Al iniciar sesión, aceptás nuestros Términos de uso y Política de privacidad

Uso

Este prompt está diseñado para usarse con coding. Copiá el contenido de arriba y pegalo en tu herramienta de IA preferida.

Para mejores resultados, personalizá los marcadores (indicados con corchetes o mayúsculas) con tus requisitos específicos.

Referencias

Categorías:coding| twitter| front-end| web-app

Discusión