Files
ietf-draft-analyzer/src/webui/templates/search_results.html
Christian Nennemann 757b781c67 Platform upgrade: semantic search, citations, readiness, tests, Docker
Major features added by 5 parallel agent teams:
- Semantic "Ask" (NL queries via FTS5 + embeddings + Claude synthesis)
- Global search across drafts, ideas, authors, gaps
- REST API expansion (14 endpoints, up from 3) with CSV/JSON export
- Citation graph visualization (D3.js, 440 nodes, 2422 edges)
- Standards readiness scoring (0-100 composite from 6 factors)
- Side-by-side draft comparison view with shared/unique analysis
- Annotation system (notes + tags per draft, DB-persisted)
- Docker deployment (Dockerfile + docker-compose with Ollama)
- Scheduled updates (cron script with log rotation)
- Pipeline health dashboard (stage progress bars, cost tracking)
- Test suite foundation (54 pytest tests covering DB, models, web data)

Fixes: compare_drafts() stubbed→working, get_authors_for_draft() bug,
source-aware analysis prompts, config env var overrides + validation,
resilient batch error handling with --retry-failed, observatory --dry-run

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:52:56 +01:00

150 lines
7.5 KiB
HTML

{% extends "base.html" %}
{% set active_page = "search" %}
{% block title %}Search: {{ query }} — IETF Draft Analyzer{% endblock %}
{% block content %}
<!-- Header -->
<div class="mb-6">
<h1 class="text-2xl font-bold text-white">Search Results</h1>
{% if query %}
<p class="text-slate-400 text-sm mt-1">
Found <span class="text-slate-300 font-medium">{{ total }}</span> results for
"<span class="text-blue-400">{{ query }}</span>"
</p>
{% else %}
<p class="text-slate-400 text-sm mt-1">Enter a search query to find drafts, ideas, authors, and gaps.</p>
{% endif %}
</div>
<!-- Search form -->
<div class="mb-8">
<form action="/search" method="get" class="flex gap-3 max-w-xl">
<input type="text" name="q" value="{{ query }}" placeholder="Search drafts, ideas, authors, gaps..."
autofocus
class="flex-1 bg-slate-800/60 border border-slate-700 rounded-lg px-4 py-2.5 text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500/30 transition">
<button type="submit" class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-500 transition-colors">
Search
</button>
</form>
</div>
{% if query %}
<!-- Drafts -->
{% if results.drafts %}
<div class="mb-8">
<h2 class="text-lg font-semibold text-white mb-3 flex items-center gap-2">
<svg class="w-5 h-5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
Drafts <span class="text-sm font-normal text-slate-500">({{ results.drafts|length }})</span>
</h2>
<div class="bg-slate-900/60 rounded-xl border border-slate-800 divide-y divide-slate-800/30">
{% for d in results.drafts %}
<div class="px-5 py-3 hover:bg-slate-800/30 transition">
<a href="/drafts/{{ d.name }}" class="text-blue-400 hover:text-blue-300 font-medium text-sm transition">
{{ d.title }}
</a>
<div class="text-xs text-slate-600 mt-0.5 font-mono">{{ d.name }}</div>
{% if d.abstract %}
<p class="text-xs text-slate-500 mt-1 line-clamp-2">{{ d.abstract }}</p>
{% endif %}
<div class="flex gap-3 mt-1 text-xs text-slate-600">
{% if d.date %}<span>{{ d.date[:10] }}</span>{% endif %}
<span>{{ d.group }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Ideas -->
{% if results.ideas %}
<div class="mb-8">
<h2 class="text-lg font-semibold text-white mb-3 flex items-center gap-2">
<svg class="w-5 h-5 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>
Ideas <span class="text-sm font-normal text-slate-500">({{ results.ideas|length }})</span>
</h2>
<div class="bg-slate-900/60 rounded-xl border border-slate-800 divide-y divide-slate-800/30">
{% for idea in results.ideas %}
<div class="px-5 py-3 hover:bg-slate-800/30 transition">
<div class="text-sm text-slate-200 font-medium">{{ idea.title }}</div>
{% if idea.description %}
<p class="text-xs text-slate-500 mt-1 line-clamp-2">{{ idea.description }}</p>
{% endif %}
<div class="flex gap-3 mt-1 text-xs text-slate-600">
{% if idea.type %}<span class="text-slate-500">{{ idea.type }}</span>{% endif %}
<a href="/drafts/{{ idea.draft_name }}" class="text-blue-500 hover:text-blue-400">{{ idea.draft_name }}</a>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Authors -->
{% if results.authors %}
<div class="mb-8">
<h2 class="text-lg font-semibold text-white mb-3 flex items-center gap-2">
<svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
Authors <span class="text-sm font-normal text-slate-500">({{ results.authors|length }})</span>
</h2>
<div class="bg-slate-900/60 rounded-xl border border-slate-800">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-0 divide-y md:divide-y-0 divide-slate-800/30">
{% for author in results.authors %}
<div class="px-5 py-3 hover:bg-slate-800/30 transition {% if not loop.last %}border-b md:border-b-0 md:border-r border-slate-800/30{% endif %}">
<div class="text-sm text-slate-200 font-medium">{{ author.name }}</div>
{% if author.affiliation %}
<div class="text-xs text-slate-500 mt-0.5">{{ author.affiliation }}</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Gaps -->
{% if results.gaps %}
<div class="mb-8">
<h2 class="text-lg font-semibold text-white mb-3 flex items-center gap-2">
<svg class="w-5 h-5 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4.5c-.77-.833-2.694-.833-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
Gaps <span class="text-sm font-normal text-slate-500">({{ results.gaps|length }})</span>
</h2>
<div class="bg-slate-900/60 rounded-xl border border-slate-800 divide-y divide-slate-800/30">
{% for gap in results.gaps %}
<div class="px-5 py-3 hover:bg-slate-800/30 transition">
<a href="/gaps/{{ gap.id }}" class="text-blue-400 hover:text-blue-300 font-medium text-sm transition">
{{ gap.topic }}
</a>
{% if gap.description %}
<p class="text-xs text-slate-500 mt-1 line-clamp-2">{{ gap.description }}</p>
{% endif %}
<div class="flex gap-3 mt-1 text-xs">
{% if gap.category %}<span class="text-slate-500">{{ gap.category }}</span>{% endif %}
{% if gap.severity %}
<span class="{% if gap.severity == 'high' %}text-red-400{% elif gap.severity == 'medium' %}text-yellow-400{% else %}text-green-400{% endif %}">
{{ gap.severity }}
</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- No results -->
{% if total == 0 %}
<div class="text-center py-16">
<svg class="w-16 h-16 mx-auto mb-4 text-slate-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<p class="text-slate-500 text-sm">No results found for "<span class="text-slate-400">{{ query }}</span>"</p>
<p class="text-slate-600 text-xs mt-2">Try different keywords or check the spelling.</p>
</div>
{% endif %}
{% endif %}
{% endblock %}