Idea quality pipeline, web UI features, academic paper
- Tighten idea extraction prompts (1-4 ideas, no sub-features) reducing 1,907 ideas to 468 across 434 drafts (78% reduction) - Add embedding-based dedup (ietf dedup-ideas) for same-draft similarity - Add novelty scoring (ietf ideas score) and filtering (ietf ideas filter) using Claude to rate ideas 1-5, removing 49 generic building blocks - Final count: 419 high-quality ideas (avg 1.1/draft) - Web UI: gap explorer with live draft generation and pre-generated demos - Web UI: D3.js author collaboration network (498 nodes, 1142 edges, 68 clusters, org filtering, interactive zoom/pan) - Academic paper: 15-page LaTeX workshop paper analyzing the 434-draft AI agent standards landscape - Save improvement ideas backlog to data/reports/improvement-ideas.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
298
src/webui/templates/draft_detail.html
Normal file
298
src/webui/templates/draft_detail.html
Normal file
@@ -0,0 +1,298 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "drafts" %}
|
||||
|
||||
{% block title %}{{ draft.name }} — IETF Draft Analyzer{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<style>
|
||||
.detail-card {
|
||||
background: linear-gradient(135deg, rgba(30, 41, 59, 0.8), rgba(30, 41, 59, 0.4));
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.score-ring {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
.score-ring::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
}
|
||||
.score-ring-high::before { background: linear-gradient(135deg, #22c55e, #4ade80); }
|
||||
.score-ring-mid::before { background: linear-gradient(135deg, #eab308, #facc15); }
|
||||
.score-ring-low::before { background: linear-gradient(135deg, #ef4444, #f87171); }
|
||||
.dim-progress {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(51, 65, 85, 0.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
.dim-progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
.dim-high { background: linear-gradient(90deg, #22c55e, #4ade80); }
|
||||
.dim-mid { background: linear-gradient(90deg, #eab308, #facc15); }
|
||||
.dim-low { background: linear-gradient(90deg, #ef4444, #f87171); }
|
||||
.idea-type-protocol { background: rgba(59, 130, 246, 0.15); color: #60a5fa; border-color: rgba(59, 130, 246, 0.3); }
|
||||
.idea-type-mechanism { background: rgba(168, 85, 247, 0.15); color: #c084fc; border-color: rgba(168, 85, 247, 0.3); }
|
||||
.idea-type-framework { background: rgba(34, 197, 94, 0.15); color: #4ade80; border-color: rgba(34, 197, 94, 0.3); }
|
||||
.idea-type-architecture { background: rgba(234, 179, 8, 0.15); color: #facc15; border-color: rgba(234, 179, 8, 0.3); }
|
||||
.idea-type-default { background: rgba(100, 116, 139, 0.15); color: #94a3b8; border-color: rgba(100, 116, 139, 0.3); }
|
||||
.ref-rfc { background: rgba(34, 197, 94, 0.15); color: #4ade80; }
|
||||
.ref-draft { background: rgba(59, 130, 246, 0.15); color: #60a5fa; }
|
||||
.ref-other { background: rgba(234, 179, 8, 0.15); color: #facc15; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Breadcrumb + Header -->
|
||||
<div class="mb-6">
|
||||
<a href="/drafts" class="inline-flex items-center gap-1.5 text-sm text-slate-500 hover:text-slate-300 transition group">
|
||||
<svg class="w-4 h-4 group-hover:-translate-x-0.5 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
Back to Explorer
|
||||
</a>
|
||||
<h1 class="text-xl font-bold text-white mt-3 leading-snug">{{ draft.title }}</h1>
|
||||
<div class="flex flex-wrap items-center gap-3 mt-2">
|
||||
<span class="text-sm text-slate-400 font-mono">{{ draft.name }}</span>
|
||||
{% if draft.rev %}
|
||||
<span class="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-500 border border-slate-700">rev {{ draft.rev }}</span>
|
||||
{% endif %}
|
||||
<span class="text-xs text-slate-600">{{ draft.date }}</span>
|
||||
{% if draft.rating %}
|
||||
<span class="score-badge {% if draft.rating.score >= 3.5 %}score-high{% elif draft.rating.score >= 2.5 %}score-mid{% else %}score-low{% endif %}">
|
||||
{{ draft.rating.score }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Left column: Main content -->
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<!-- Abstract -->
|
||||
<div class="detail-card rounded-xl border border-slate-800 p-6">
|
||||
<h2 class="text-sm font-semibold text-slate-300 mb-3 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"/></svg>
|
||||
Abstract
|
||||
</h2>
|
||||
<p class="text-sm text-slate-400 leading-relaxed">{{ draft.abstract or "No abstract available." }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Rating Analysis -->
|
||||
{% if draft.rating %}
|
||||
<div class="detail-card rounded-xl border border-slate-800 p-6">
|
||||
<h2 class="text-sm font-semibold text-slate-300 mb-3 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
|
||||
AI Rating Analysis
|
||||
</h2>
|
||||
{% if draft.rating.summary %}
|
||||
<p class="text-sm text-slate-400 mb-5 leading-relaxed">{{ draft.rating.summary }}</p>
|
||||
{% endif %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{% for dim, label, icon in [
|
||||
("novelty", "Novelty", "M13 10V3L4 14h7v7l9-11h-7z"),
|
||||
("maturity", "Maturity", "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"),
|
||||
("overlap", "Overlap", "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"),
|
||||
("momentum", "Momentum", "M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"),
|
||||
("relevance", "Relevance", "M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z")
|
||||
] %}
|
||||
{% set val = draft.rating[dim] %}
|
||||
<div class="bg-slate-800/30 rounded-lg p-4 border border-slate-800/50">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-3.5 h-3.5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ icon }}"/></svg>
|
||||
<span class="text-xs font-semibold text-slate-300 uppercase tracking-wide">{{ label }}</span>
|
||||
</div>
|
||||
<span class="text-lg font-bold {% if val >= 4 %}text-green-400{% elif val >= 3 %}text-amber-400{% else %}text-red-400{% endif %}">{{ val }}<span class="text-xs text-slate-600 font-normal">/5</span></span>
|
||||
</div>
|
||||
<div class="dim-progress mb-2">
|
||||
<div class="dim-progress-fill {% if val >= 4 %}dim-high{% elif val >= 3 %}dim-mid{% else %}dim-low{% endif %}" style="width: {{ val * 20 }}%"></div>
|
||||
</div>
|
||||
{% if draft.rating[dim + '_note'] %}
|
||||
<p class="text-xs text-slate-500 leading-relaxed">{{ draft.rating[dim + '_note'] }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Ideas -->
|
||||
{% if draft.ideas %}
|
||||
<div class="detail-card rounded-xl border border-slate-800 p-6">
|
||||
<h2 class="text-sm font-semibold text-slate-300 mb-4 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-slate-500" 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>
|
||||
Extracted Ideas <span class="text-slate-600 font-normal">({{ draft.ideas|length }})</span>
|
||||
</h2>
|
||||
<div class="space-y-3">
|
||||
{% for idea in draft.ideas %}
|
||||
<div class="bg-slate-800/30 rounded-lg p-4 border border-slate-800/50">
|
||||
<div class="flex items-start gap-2 mb-1">
|
||||
<span class="text-sm font-medium text-slate-200 leading-snug">{{ idea.title }}</span>
|
||||
{% if idea.type %}
|
||||
{% set type_lower = idea.type|lower %}
|
||||
<span class="flex-shrink-0 px-2 py-0.5 rounded-full text-[10px] font-medium border
|
||||
{% if type_lower == 'protocol' %}idea-type-protocol
|
||||
{% elif type_lower == 'mechanism' %}idea-type-mechanism
|
||||
{% elif type_lower == 'framework' %}idea-type-framework
|
||||
{% elif type_lower == 'architecture' %}idea-type-architecture
|
||||
{% else %}idea-type-default{% endif %}">
|
||||
{{ idea.type }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if idea.description %}
|
||||
<p class="text-xs text-slate-500 leading-relaxed mt-1">{{ idea.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Right column: Sidebar -->
|
||||
<div class="space-y-6">
|
||||
<!-- Score Card -->
|
||||
{% if draft.rating %}
|
||||
<div class="detail-card rounded-xl border border-slate-800 p-6 text-center">
|
||||
<div class="score-ring {% if draft.rating.score >= 3.5 %}score-ring-high{% elif draft.rating.score >= 2.5 %}score-ring-mid{% else %}score-ring-low{% endif %}">
|
||||
<div>
|
||||
<div class="text-3xl font-bold {% if draft.rating.score >= 3.5 %}text-green-400{% elif draft.rating.score >= 2.5 %}text-amber-400{% else %}text-red-400{% endif %}">
|
||||
{{ draft.rating.score }}
|
||||
</div>
|
||||
<div class="text-[10px] text-slate-500 uppercase tracking-wider">Score</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Mini dimension summary -->
|
||||
<div class="mt-4 grid grid-cols-5 gap-1 text-center">
|
||||
{% for dim, abbr in [("novelty","N"), ("maturity","M"), ("overlap","O"), ("momentum","Mo"), ("relevance","R")] %}
|
||||
{% set v = draft.rating[dim] %}
|
||||
<div>
|
||||
<div class="text-xs font-bold {% if v >= 4 %}text-green-400{% elif v >= 3 %}text-amber-400{% else %}text-red-400{% endif %}">{{ v }}</div>
|
||||
<div class="text-[9px] text-slate-600 uppercase">{{ abbr }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="detail-card rounded-xl border border-slate-800 p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-300 mb-3 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Metadata
|
||||
</h2>
|
||||
<dl class="space-y-2.5 text-sm">
|
||||
<div class="flex justify-between"><dt class="text-slate-500 text-xs">Date</dt><dd class="text-slate-300">{{ draft.date }}</dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500 text-xs">Revision</dt><dd class="text-slate-300">{{ draft.rev or 'N/A' }}</dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500 text-xs">Pages</dt><dd class="text-slate-300">{{ draft.pages or 'N/A' }}</dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500 text-xs">Words</dt><dd class="text-slate-300">{{ '{:,}'.format(draft.words) if draft.words else 'N/A' }}</dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500 text-xs">Working Group</dt><dd class="text-slate-300">{{ draft.group }}</dd></div>
|
||||
</dl>
|
||||
<div class="mt-4 space-y-2">
|
||||
<a href="{{ draft.url }}" target="_blank" rel="noopener"
|
||||
class="flex items-center justify-center gap-2 px-3 py-2 bg-blue-600 text-white rounded-lg text-xs font-medium hover:bg-blue-500 transition">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
|
||||
View on Datatracker
|
||||
</a>
|
||||
{% if draft.text_url %}
|
||||
<a href="{{ draft.text_url }}" target="_blank" rel="noopener"
|
||||
class="flex items-center justify-center gap-2 px-3 py-2 border border-slate-700 text-slate-300 rounded-lg text-xs font-medium hover:border-slate-500 hover:text-white transition">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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>
|
||||
Read Full Text
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authors -->
|
||||
{% if draft.authors %}
|
||||
<div class="detail-card rounded-xl border border-slate-800 p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-300 mb-3 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-slate-500" 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-slate-600 font-normal">({{ draft.authors|length }})</span>
|
||||
</h2>
|
||||
<ul class="space-y-2.5">
|
||||
{% for a in draft.authors %}
|
||||
<li class="flex items-start gap-2">
|
||||
<div class="w-7 h-7 rounded-full bg-slate-800 border border-slate-700 flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<span class="text-xs font-semibold text-slate-400">{{ a.name[0]|upper if a.name else '?' }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/drafts?q={{ a.name | urlencode }}" class="text-sm text-blue-400 hover:text-blue-300 transition">{{ a.name }}</a>
|
||||
{% if a.affiliation %}
|
||||
<div class="text-xs text-slate-500">{{ a.affiliation }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Categories -->
|
||||
{% if draft.rating and draft.rating.categories %}
|
||||
<div class="detail-card rounded-xl border border-slate-800 p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-300 mb-3 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
|
||||
Categories
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{% for cat in draft.rating.categories %}
|
||||
<a href="/drafts?cat={{ cat }}"
|
||||
class="px-2.5 py-1 rounded-full text-xs bg-slate-800/60 text-slate-400 border border-slate-700 hover:border-blue-500 hover:text-blue-400 transition">
|
||||
{{ cat }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- References -->
|
||||
{% if draft.refs %}
|
||||
<div class="detail-card rounded-xl border border-slate-800 p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-300 mb-3 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>
|
||||
References <span class="text-slate-600 font-normal">({{ draft.refs|length }})</span>
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-1.5 max-h-48 overflow-y-auto">
|
||||
{% for ref in draft.refs %}
|
||||
{% if ref.type == 'rfc' %}
|
||||
<a href="https://www.rfc-editor.org/rfc/{{ ref.id }}" target="_blank" rel="noopener"
|
||||
class="px-2 py-0.5 rounded text-[10px] font-medium ref-rfc hover:opacity-80 transition">
|
||||
RFC {{ ref.id.replace('rfc', '') }}
|
||||
</a>
|
||||
{% elif ref.type == 'draft' %}
|
||||
<a href="/drafts/{{ ref.id }}"
|
||||
class="px-2 py-0.5 rounded text-[10px] font-medium ref-draft hover:opacity-80 transition">
|
||||
{{ ref.id }}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="px-2 py-0.5 rounded text-[10px] font-medium ref-other">
|
||||
{{ ref.type|upper }} {{ ref.id }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user