- Add image intake service with format validation (JPEG, PNG, WebP) and EXIF/GPS stripping - Enforce strict single-image rule across Web and Signal attachment channels - Implement token-optimized vision downscaling and JPEG compression - Add IMAGE_CONTEXT pipeline stage with OmniRoute vision routing and resilient failover - Seed and manage versioned idea-image-interpreter prompt in catalog - Update Web UI with responsive image picker, preview chip, and Visual Context tab - Add comprehensive automated test suite in test_image_intake.py - Update README and Labyricorn devlog
142 lines
7.6 KiB
HTML
142 lines
7.6 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{% if current_state == 'TRASHED' %}Trash Bin - ThinkStorm{% else %}Browse Incubating Ideas - ThinkStorm{% endif %}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div style="margin-bottom: 2rem;">
|
|
<h1 style="font-size: 2.2rem; margin-bottom: 0.5rem;">{% if current_state == 'TRASHED' %}🗑️ Trash Bin{% else %}Incubation Catalog{% endif %}</h1>
|
|
<p style="color: var(--text-secondary);">
|
|
{% if current_state == 'TRASHED' %}
|
|
Discarded submissions and unpromising ideas. Items in trash are retrievable at any time until permanently emptied.
|
|
{% else %}
|
|
Explore anonymous submissions, source-backed research dossiers, and claimed work tracks.
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Toolbar -->
|
|
<div class="ideas-toolbar">
|
|
<div class="filter-pills">
|
|
<a href="/ideas" class="filter-pill {% if not current_state %}active{% endif %}">Available & Active</a>
|
|
<a href="/ideas?state=AVAILABLE" class="filter-pill {% if current_state == 'AVAILABLE' %}active{% endif %}">Available</a>
|
|
<a href="/ideas?state=CLAIMED" class="filter-pill {% if current_state == 'CLAIMED' %}active{% endif %}">Claimed</a>
|
|
<a href="/ideas?state=ACTIVE" class="filter-pill {% if current_state == 'ACTIVE' %}active{% endif %}">Active</a>
|
|
<a href="/ideas?state=COMPLETED" class="filter-pill {% if current_state == 'COMPLETED' %}active{% endif %}">Completed</a>
|
|
<a href="/ideas?state=ALL" class="filter-pill {% if current_state == 'ALL' %}active{% endif %}">All Ideas</a>
|
|
<a href="/ideas?state=TRASHED" class="filter-pill filter-pill-trash {% if current_state == 'TRASHED' %}active{% endif %}">
|
|
🗑️ Trash {% if trashed_count and trashed_count > 0 %}<span class="badge badge-trashed" style="font-size:0.7rem; margin-left:0.25rem; padding:0.1rem 0.4rem;">{{ trashed_count }}</span>{% endif %}
|
|
</a>
|
|
</div>
|
|
|
|
<form method="GET" action="/ideas" style="display:flex; gap:0.5rem; flex:1; max-width:380px;">
|
|
{% if current_state %}<input type="hidden" name="state" value="{{ current_state }}">{% endif %}
|
|
<input type="text" name="q" value="{{ search_query or '' }}" class="form-input" placeholder="Search ideas or keywords..." style="padding:0.45rem 0.85rem; font-size:0.9rem;">
|
|
<button type="submit" class="btn btn-secondary btn-sm">Search</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Trash Banner when in Trash view -->
|
|
{% if current_state == 'TRASHED' %}
|
|
<div class="glass-card" style="padding:1.25rem 1.5rem; margin-bottom:1.5rem; border-color:rgba(239,68,68,0.3); background:rgba(239,68,68,0.06); display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:1rem;">
|
|
<div>
|
|
<h3 style="font-size:1.1rem; color:#f87171; margin-bottom:0.25rem; display:flex; align-items:center; gap:0.5rem;">
|
|
<span>🗑️ Trash Management</span>
|
|
<span class="badge badge-trashed">{{ ideas|length }} item(s)</span>
|
|
</h3>
|
|
<p style="color:var(--text-secondary); font-size:0.85rem; margin:0;">
|
|
Items in the trash are withheld from active incubation. You can restore ideas back to their previous state, or permanently empty the trash.
|
|
</p>
|
|
</div>
|
|
{% if ideas and ideas|length > 0 %}
|
|
<button onclick="emptyTrash()" class="btn btn-danger" style="display:flex; align-items:center; gap:0.4rem;">
|
|
<span>🗑️ Empty Trash</span>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Grid -->
|
|
{% if ideas and ideas|length > 0 %}
|
|
<div class="ideas-grid">
|
|
{% for idea in ideas %}
|
|
<div class="glass-card idea-card interactive {% if idea.lifecycle_state == 'TRASHED' %}card-trashed{% endif %}">
|
|
<div class="idea-card-header">
|
|
<a href="/ideas/{{ idea.id }}" class="idea-id-link">{{ idea.id }}</a>
|
|
|
|
<div style="display:flex; align-items:center; gap:0.5rem;">
|
|
{% if idea.lifecycle_state != 'TRASHED' %}
|
|
<div class="maturity-meter" title="Enrichment Level {{ idea.enrichment_level }}/5">
|
|
<span style="font-size:0.7rem;">L{{ idea.enrichment_level }}</span>
|
|
<div class="dots">
|
|
{% for i in range(1, 6) %}
|
|
<div class="dot {% if idea.enrichment_level >= i %}active{% endif %}"></div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
<span class="badge badge-{{ idea.lifecycle_state|lower }}">{{ idea.lifecycle_state }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="idea-card-title">
|
|
<a href="/ideas/{{ idea.id }}" style="color:var(--text-primary);">{{ idea.title }}</a>
|
|
</h2>
|
|
|
|
<p class="idea-card-summary">{{ idea.summary }}</p>
|
|
|
|
{% if idea.tags and idea.tags|length > 0 %}
|
|
<div class="tag-list">
|
|
{% for t in idea.tags %}
|
|
<span class="tag-item">#{{ t }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="idea-card-footer">
|
|
{% if idea.lifecycle_state == 'TRASHED' %}
|
|
<div style="display:flex; gap:0.5rem; width:100%; justify-content:space-between; align-items:center;">
|
|
<div style="display:flex; gap:0.4rem;">
|
|
<button onclick="restoreIdea('{{ idea.id }}')" class="btn btn-secondary btn-sm" style="color:#34d399;" title="Restore this idea">
|
|
♻️ Restore
|
|
</button>
|
|
<button onclick="deleteIdeaPermanently('{{ idea.id }}')" class="btn btn-danger btn-sm" title="Permanently delete from database">
|
|
❌ Delete
|
|
</button>
|
|
</div>
|
|
<a href="/ideas/{{ idea.id }}" style="font-weight:600; font-size:0.85rem;">View Dossier →</a>
|
|
</div>
|
|
{% else %}
|
|
<div>
|
|
{% if idea.claimed_by %}
|
|
<span>Claimed by <strong>{{ idea.claimed_by }}</strong></span>
|
|
{% else %}
|
|
<span style="color:var(--color-success);">Ready to claim</span>
|
|
{% endif %}
|
|
</div>
|
|
<div style="display:flex; align-items:center; gap:0.6rem;">
|
|
{% if idea.enrichment_level < 4 or idea.lifecycle_state in ['SUBMITTED', 'DUPLICATE', 'QUARANTINED'] %}
|
|
<button onclick="event.stopPropagation(); event.preventDefault(); reprocessIdea('{{ idea.id }}');" class="btn btn-warning btn-sm" style="padding:0.25rem 0.6rem; font-size:0.8rem; font-weight:700;" title="Trigger research and feasibility processing">⚡ Process</button>
|
|
{% endif %}
|
|
<button onclick="event.stopPropagation(); event.preventDefault(); trashIdea('{{ idea.id }}');" class="btn btn-secondary btn-sm" style="padding:0.25rem 0.6rem; color:#f87171; font-size:0.8rem;" title="Move to Trash">🗑️ Trash</button>
|
|
<a href="/ideas/{{ idea.id }}" style="font-weight:600; font-size:0.85rem;">View Dossier →</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="glass-card" style="padding: 3rem; text-align: center; margin-top: 1rem;">
|
|
{% if current_state == 'TRASHED' %}
|
|
<h3 style="margin-bottom: 0.5rem;">Trash is Empty</h3>
|
|
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">There are currently no discarded or trashed ideas.</p>
|
|
<a href="/ideas" class="btn btn-secondary">Browse Active Ideas</a>
|
|
{% else %}
|
|
<h3 style="margin-bottom: 0.5rem;">No ideas found</h3>
|
|
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">There are currently no ideas matching the selected filters.</p>
|
|
<a href="/" class="btn btn-primary">⚡ Submit an Idea</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|