Add Auto-Refine mode, composer UX, select dropdown fixes

Auto-Refine mode (21st mode):
- AI strategist analyzes content type and quality
- Selects 3-5 optimal refinement stages from 8 available
  (validate, critique, expand, structure, stakeholder, clarity,
  edge_cases, align)
- Executes stages sequentially with output chaining
- Final synthesis produces polished version
- Stages are content-aware — PRD gets different pipeline than essay
- Saved to pipeline_runs DB

Composer UX overhaul:
- Initial state: full-screen centered composer overlay
- Mode grid + models + prompt front-and-center for new users
- On Run: composer closes, output takes full screen width
- "New Prompt" button in header nav bar (not floating)
- Close button (×) on composer overlay
- Works across all 4 themes + mobile

Dropdown fixes:
- Dark theme: select options get solid #1a1d23 bg
- Modern theme: select options get solid #18181b bg
- Light/Reddit: select options get white bg with dark text
- Native <option> elements now readable in all themes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-03-29 05:12:35 -05:00
parent 713f18a65f
commit 0d09bb5293

View File

@ -149,6 +149,9 @@ body.theme-light .output-card, body.theme-reddit .output-card { box-shadow: 0 1p
body.theme-light .output-card .card-body, body.theme-reddit .output-card .card-body { background: var(--surface); }
body.theme-light textarea, body.theme-reddit textarea { background: var(--surface) !important; color: var(--text) !important; }
body.theme-light input, body.theme-light select, body.theme-reddit input, body.theme-reddit select { background: var(--surface) !important; color: var(--text) !important; border-color: var(--border) !important; }
body.theme-light select option, body.theme-reddit select option { background: var(--surface); color: var(--text); }
body.theme-light .config-row select, body.theme-reddit .config-row select { background: #fff !important; color: #1a1d23 !important; }
body.theme-light .config-row select option, body.theme-reddit .config-row select option { background: #fff; color: #1a1d23; }
body.theme-light .threat-card, body.theme-reddit .threat-card { background: var(--surface); }
body.theme-light .threat-card.critical, body.theme-reddit .threat-card.critical { background: rgba(220,38,38,0.03); }
body.theme-light .threat-card.high, body.theme-reddit .threat-card.high { background: rgba(217,119,6,0.03); }
@ -261,6 +264,12 @@ body.theme-reddit .prov-badge.ollama { background: rgba(70,209,96,0.08); color:
body.theme-reddit header { border-bottom-width: 1px; }
body.theme-reddit ::-webkit-scrollbar-thumb { background: rgba(255,69,0,0.15); border-radius: 8px; border: 2px solid transparent; background-clip: content-box; }
body.theme-reddit ::-webkit-scrollbar-thumb:hover { background: rgba(255,69,0,0.3); border-radius: 8px; border: 2px solid transparent; background-clip: content-box; }
body.theme-light .new-prompt-btn, body.theme-reddit .new-prompt-btn { color: #fff; }
body.theme-light .composer-close, body.theme-reddit .composer-close { background: var(--surface); }
body.theme-reddit .new-prompt-btn { border-radius: 20px; }
body.theme-reddit .composer-close { border-radius: 20px; }
body.theme-modern .new-prompt-btn { border-radius: 8px; color: #fff; }
body.theme-modern .composer-close { border-radius: 8px; border-width: 1px; backdrop-filter: blur(12px); background: var(--surface); }
body.theme-light .repipe-overlay, body.theme-reddit .repipe-overlay { background: rgba(0,0,0,0.3); }
body.theme-light .repipe-modal, body.theme-reddit .repipe-modal { background: var(--surface); border-color: var(--border); }
body.theme-light .repipe-text, body.theme-reddit .repipe-text { background: var(--bg); border-color: var(--border); color: var(--text); }
@ -337,6 +346,9 @@ body.theme-modern .tag-ok { background: rgba(34,197,94,0.08); color: var(--green
body.theme-modern .tag-time { background: rgba(59,130,246,0.08); color: var(--accent2); border-color: rgba(59,130,246,0.15); }
body.theme-modern .tag-mode { background: rgba(139,92,246,0.08); color: #a78bfa; border-color: rgba(139,92,246,0.15); }
body.theme-modern input, body.theme-modern select, body.theme-modern textarea { border-radius: 8px; border-width: 1px; background: rgba(255,255,255,0.04) !important; transition: border-color 0.2s, box-shadow 0.2s; }
body.theme-modern select option { background: #18181b; color: #fafafa; }
body.theme-modern .config-row select { background: #18181b !important; color: #fafafa !important; }
body.theme-modern .config-row select option { background: #18181b; color: #fafafa; }
body.theme-modern input:focus, body.theme-modern textarea:focus { border-color: var(--accent) !important; box-shadow: 0 0 0 3px rgba(59,130,246,0.15) !important; }
body.theme-modern header { border-bottom: 1px solid var(--border); }
body.theme-modern .login-box { border-radius: 16px; backdrop-filter: blur(24px); box-shadow: 0 4px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.04) inset; }
@ -1832,6 +1844,7 @@ HTML = r"""
.config-row { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; font-size: 12px; }
.config-row label { width: 90px; color: var(--text2); flex-shrink: 0; font-weight: 600; font-family: 'JetBrains Mono', monospace; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
.config-row select, .config-row input { flex: 1; background: rgba(0,0,0,0.4); border: 2px solid var(--border); color: var(--text); border-radius: 2px; padding: 6px 8px; font-size: 12px; }
.config-row select option, select option { background: #1a1d23; color: #e8e6e3; }
.config-row select:focus, .config-row input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.pipeline-step { display: flex; align-items: center; gap: 8px; padding: 7px; margin-bottom: 4px; background: rgba(0,0,0,0.25); border: 1px solid var(--border); border-radius: 2px; font-size: 12px; }
.pipeline-step .step-num { width: 22px; height: 22px; background: var(--accent); color: #08090c; border-radius: 2px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 11px; flex-shrink: 0; font-family: 'JetBrains Mono', monospace; }
@ -1915,8 +1928,26 @@ HTML = r"""
.output-card .card-body::-webkit-scrollbar { width: 4px; }
.m-toggle { display: none; }
.m-collapse { display: block !important; }
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
@media (max-width: 768px) { .m-toggle { display: flex; } .m-collapse { display: none !important; } .m-collapse.open { display: block !important; } }
/* Composer overlay mode */
.composer-active .grid { display: block; }
.composer-active .left-scroll { position: fixed; inset: 0; z-index: 100; background: var(--bg); display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding: 60px 20px 40px; overflow-y: auto; max-height: 100vh; }
.composer-active .left-scroll > .panel,
.composer-active .left-scroll > .m-collapse { width: 100%; max-width: 640px; }
.composer-active .left-scroll > .m-toggle { width: 100%; max-width: 640px; }
.composer-active .grid > .panel:last-child { display: none; }
.composer-close { display: none; position: fixed; top: 16px; right: 20px; z-index: 110; background: none; border: 2px solid var(--border); border-radius: 2px; color: var(--text2); font-size: 18px; width: 32px; height: 32px; cursor: pointer; font-family: 'JetBrains Mono', monospace; transition: all 0.15s; line-height: 1; }
.composer-close:hover { border-color: var(--accent); color: var(--accent); }
.composer-active .composer-close { display: block; }
/* Output-focused mode (after run) */
.output-focused .grid { grid-template-columns: 1fr; }
.output-focused .left-scroll { display: none; }
.output-focused .grid > .panel:last-child { width: 100%; }
.new-prompt-btn { display: none; z-index: 90; background: var(--accent); color: #08090c; border: none; border-radius: 2px; padding: 5px 14px; font-size: 10px; font-weight: 700; cursor: pointer; font-family: 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 1px; transition: all 0.2s; }
.new-prompt-btn:hover { opacity: 0.85; }
.output-focused .new-prompt-btn { display: inline-block; }
/* Theme adjustments for composer */
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } .composer-active .left-scroll { padding: 40px 12px 30px; } }
@media (max-width: 768px) { .m-toggle { display: flex; } .m-collapse { display: none !important; } .m-collapse.open { display: block !important; } .composer-active .left-scroll { padding: 30px 10px 20px; } }
.card-actions { display: flex; gap: 4px; padding: 6px 14px 10px; }
.card-act { background: none; border: 2px solid var(--border); border-radius: 2px; color: var(--text2); font-size: 9px; padding: 3px 10px; cursor: pointer; transition: all 0.15s; font-family: 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 0.5px; }
.card-act:hover { border-color: var(--accent); color: var(--accent); }
@ -2014,6 +2045,7 @@ HTML = r"""
<h1><span>LLM</span> Team</h1>
<div class="badge" id="model-count"><span class="dot"></span>0 models</div>
<nav style="margin-left:auto;display:flex;align-items:center;gap:4px">
<button class="new-prompt-btn" onclick="openComposer()">New Prompt</button>
<a href="/history" style="color:var(--text2);text-decoration:none;font-size:10px;padding:5px 10px;border:2px solid var(--border);border-radius:2px;font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:0.5px">History</a>
<a href="/lab" style="color:var(--green);text-decoration:none;font-size:10px;padding:5px 10px;border:2px solid rgba(74,222,128,0.2);border-radius:2px;font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:0.5px">Lab</a>
<a href="/admin" style="color:var(--text2);text-decoration:none;font-size:10px;padding:5px 10px;border:2px solid var(--border);border-radius:2px;font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:0.5px">Admin</a>
@ -2022,6 +2054,7 @@ HTML = r"""
<a href="/logout" style="color:var(--text2);text-decoration:none;font-size:9px;padding:4px 8px;opacity:0.4;font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:0.5px">Logout</a>
</nav>
</header>
<button class="composer-close" onclick="closeComposer()" title="Close">&times;</button>
<div class="grid">
<div class="left-scroll">
<div class="m-toggle" onclick="this.classList.toggle('open');document.getElementById('mode-collapse').classList.toggle('open')" id="mode-toggle">Mode: <span id="mode-label">Brainstorm</span></div>
@ -2051,6 +2084,7 @@ HTML = r"""
<div class="mode-grid" style="grid-template-columns:repeat(3,1fr);margin-bottom:16px">
<div class="mode-tab" data-mode="research" onclick="setMode('research')" style="border-color:var(--green);border-width:1px">Research<small>Auto brief</small></div>
<div class="mode-tab" data-mode="eval" onclick="setMode('eval')" style="border-color:var(--orange);border-width:1px">Model Eval<small>Benchmark</small></div>
<div class="mode-tab" data-mode="refine" onclick="setMode('refine')" style="border-color:var(--accent);border-width:1px">Auto-Refine<small>AI pipeline</small></div>
<div class="mode-tab" data-mode="extract" onclick="setMode('extract')" style="border-color:var(--blue);border-width:1px">Knowledge<small>Extract facts</small></div>
</div>
<div class="mode-desc" id="mode-desc">All models answer in parallel, then one synthesizes the best parts into a final answer.</div>
@ -2204,6 +2238,14 @@ HTML = r"""
<option value="guides">GUIDES.md</option>
</select></div>
</div>
<!-- AUTO-REFINE -->
<div id="config-refine" class="config-section" style="display:none">
<h2>Auto-Refine Pipeline</h2>
<div class="config-row"><label>Orchestrator</label><select id="refine-orchestrator"></select></div>
<div class="model-list" id="ml-refine"></div>
<div class="config-row"><label>Max Stages</label><input type="number" id="refine-stages" value="4" min="2" max="6" style="width:60px;flex:none"></div>
<div style="font-size:10px;color:var(--text2);margin-top:6px;line-height:1.5;font-family:'JetBrains Mono',monospace">AI analyzes your content, picks the best refinement stages, and runs them in sequence. Paste a finished draft above.</div>
</div>
</div>
</div><!-- end m-collapse -->
<div class="panel">
@ -2222,12 +2264,30 @@ HTML = r"""
</div>
</div>
<script>
// COMPOSER MODE
function openComposer() {
document.querySelector('.container').classList.remove('output-focused');
document.querySelector('.container').classList.add('composer-active');
document.getElementById('prompt').focus();
}
function closeComposer() {
document.querySelector('.container').classList.remove('composer-active');
// If there's output, go to output-focused mode
if (document.querySelectorAll('.output-card').length > 0) {
document.querySelector('.container').classList.add('output-focused');
}
}
// Start in composer mode on load
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('.container').classList.add('composer-active');
});
const COLORS = ['#6366f1','#22c55e','#f59e0b','#3b82f6','#ef4444','#ec4899','#14b8a6','#f97316'];
let availableModels = [];
let currentMode = 'brainstorm';
const modelSets = {};
const ML_IDS = ['ml-brainstorm','ml-validator','ml-roundrobin','ml-consensus','ml-ladder','ml-tournament','ml-evolution','ml-blindassembly','ml-mesh','ml-hallucination','ml-research','ml-eval'];
const ML_IDS = ['ml-brainstorm','ml-validator','ml-roundrobin','ml-consensus','ml-ladder','ml-tournament','ml-evolution','ml-blindassembly','ml-mesh','ml-hallucination','ml-research','ml-eval','ml-refine'];
const MODE_DESCS = {
brainstorm: 'All models answer in parallel, then one synthesizes the best parts.',
@ -2249,7 +2309,8 @@ const MODE_DESCS = {
timeloop: 'CHAOS MODE: Model answers, then a Chaos Agent says "your answer caused a catastrophe!" and describes what went wrong. Answerer must fix it. But each fix causes a NEW catastrophe. Loop until bulletproof!',
research: 'AUTONOMOUS: Scout generates research questions, multiple models research in parallel, fact-checker verifies, synthesizer produces a structured brief. Full pipeline saved to DB.',
eval: 'AUTONOMOUS: Same prompts sent to all selected models. Judge scores each on accuracy, reasoning, clarity. Produces a ranked leaderboard across multiple rounds.',
extract: 'AUTONOMOUS: Extracts structured facts, entities, and relationships from text or local docs. Verifier cross-checks claims. Output saved as queryable JSON.'
extract: 'AUTONOMOUS: Extracts structured facts, entities, and relationships from text or local docs. Verifier cross-checks claims. Output saved as queryable JSON.',
refine: 'AUTONOMOUS: AI analyzes your content, selects the best refinement stages (critique, expand, structure, validate, etc.), and runs them in the optimal order. Turns a good draft into a polished final version.'
};
const SAMPLE_PROMPTS = {
@ -2352,6 +2413,11 @@ const SAMPLE_PROMPTS = {
'The James Webb Space Telescope launched December 25, 2021. It orbits the Sun-Earth L2 point, 1.5 million km from Earth. Its 6.5m primary mirror has 18 gold-plated beryllium segments.',
'Extract all entities, relationships, and claims from the Apollo 11 Wikipedia article. Structure as people, organizations, dates, technical specs, and disputed claims.',
'Process the Paris Climate Agreement. Extract signatory obligations by category, numeric targets, compliance mechanisms, financial commitments, and identify legally binding vs. aspirational obligations.'
],
refine: [
'Our product is a local-first data platform that ingests CSV, JSON, and PDF files into a Parquet-based lakehouse with SQL querying and AI-powered semantic search. Target users are small staffing companies with legacy data silos.',
'PRD: We are building a multi-model AI orchestration tool. Users select a mode (brainstorm, debate, pipeline, etc.), pick which LLMs to use, and enter a prompt. The system coordinates the models and streams results back. Key differentiator: runs 100% locally with no cloud dependency.',
'Technical spec: Authentication system using JWT tokens with refresh rotation. Users authenticate via username/password, receive access token (15min) and refresh token (7 days). Refresh tokens are single-use with family detection for replay attacks. Session management via Redis with configurable TTL.'
]
};
@ -2424,7 +2490,7 @@ function populateAllSelects() {
'staircase-challenger','drift-target','drift-analyzer','mesh-synthesizer','halluc-answerer',
'timeloop-answerer','timeloop-chaos',
'research-scout','research-checker','research-synth',
'eval-judge','extract-model','extract-verifier'];
'eval-judge','extract-model','extract-verifier','refine-orchestrator'];
ids.forEach(id => {
const el = document.getElementById(id);
if (!el) return;
@ -2504,6 +2570,7 @@ function buildConfig() {
case 'research': c.scout = getVal('research-scout'); c.models = getModels('ml-research'); c.checker = getVal('research-checker'); c.synthesizer = getVal('research-synth'); c.num_questions = getNum('research-questions'); break;
case 'eval': c.models = getModels('ml-eval'); c.judge = getVal('eval-judge'); c.eval_type = getVal('eval-type'); c.rounds = getNum('eval-rounds'); break;
case 'extract': c.extractor = getVal('extract-model'); c.verifier = getVal('extract-verifier'); c.source = getVal('extract-source'); break;
case 'refine': c.orchestrator = getVal('refine-orchestrator'); c.models = getModels('ml-refine'); c.max_stages = getNum('refine-stages'); break;
}
return c;
}
@ -2560,6 +2627,10 @@ function updateProgressMetrics() {
async function runTeam() {
var config = buildConfig();
if (!config) return;
// Switch to output-focused mode
var ct = document.querySelector('.container');
ct.classList.remove('composer-active');
ct.classList.add('output-focused');
var btn = document.getElementById('run-btn');
btn.disabled = true; btn.textContent = 'Running...';
var output = document.getElementById('output');
@ -7074,6 +7145,7 @@ def run_team():
"staircase": run_staircase, "drift": run_drift, "mesh": run_mesh,
"hallucination": run_hallucination, "timeloop": run_timeloop,
"research": run_research, "eval": run_eval, "extract": run_extract,
"refine": run_refine,
}
run_id = str(_uuid.uuid4())[:8]
@ -8157,6 +8229,159 @@ def run_extract(config):
_save_pipeline("extract", prompt or source, steps, result_data, all_models, start)
def run_refine(config):
"""Auto-Refine: AI analyzes content, selects the best sequence of modes, executes them, synthesizes final version."""
import time
start = time.time() * 1000
prompt = config["prompt"]
orchestrator = config.get("orchestrator", "qwen2.5:latest")
workers = config.get("models", ["qwen2.5:latest", "mistral:latest"])
max_stages = config.get("max_stages", 5)
yield sse({"type": "clear"})
steps = []
all_models = [orchestrator] + workers
# Stage 1: Analyze the content and plan the refinement pipeline
yield sse({"type": "status", "message": "Analyzing content and planning refinement pipeline..."})
yield sse({"type": "progress", "current": 0, "total": 3, "label": "analyzing"})
plan_prompt = f"""You are a refinement strategist. Analyze this content and determine the optimal sequence of refinement stages to improve it.
CONTENT TO REFINE:
{prompt[:8000]}
AVAILABLE REFINEMENT STAGES (pick 3-{max_stages} in the best order):
- VALIDATE: Fact-check claims, verify accuracy, flag unsupported statements
- CRITIQUE: Find weaknesses, gaps, contradictions, missing perspectives
- EXPAND: Add depth to thin sections, elaborate on key points, fill gaps identified by critique
- STRUCTURE: Improve organization, flow, headings, logical progression
- STAKEHOLDER: Analyze from multiple stakeholder perspectives (user, developer, business, ops)
- CLARITY: Simplify language, remove jargon, improve readability, sharpen wording
- EDGE_CASES: Identify edge cases, failure modes, what-ifs, risks not addressed
- ALIGN: Check alignment between stated goals and actual content does the document deliver on its promise?
Respond with ONLY a JSON object:
{{
"content_type": "what this content is (PRD, essay, proposal, spec, etc)",
"current_quality": "brief assessment of current state",
"stages": ["STAGE1", "STAGE2", "STAGE3"],
"reasoning": "why this order, one sentence per stage"
}}
Pick ONLY the stages that will meaningfully improve THIS specific content. Not every document needs every stage. Order matters dependencies first."""
try:
plan_raw = safe_query(orchestrator, plan_prompt)
yield sse({"type": "response", "model": orchestrator, "text": plan_raw, "role": "strategist"})
steps.append({"step": "plan", "model": orchestrator, "output": plan_raw[:1000]})
except Exception as e:
yield sse({"type": "response", "model": orchestrator, "text": f"Planning failed: {e}", "role": "error"})
return
# Parse the plan
plan_text = plan_raw.strip()
if "```" in plan_text:
plan_text = plan_text.split("```")[1]
if plan_text.startswith("json"):
plan_text = plan_text[4:]
start_idx = plan_text.find("{")
end_idx = plan_text.rfind("}") + 1
try:
plan = json.loads(plan_text[start_idx:end_idx])
stages = plan.get("stages", ["CRITIQUE", "EXPAND", "STRUCTURE"])[:max_stages]
except Exception:
stages = ["CRITIQUE", "EXPAND", "STRUCTURE"]
plan = {"content_type": "document", "current_quality": "unknown", "reasoning": "fallback plan"}
content_type = plan.get("content_type", "document")
total_stages = len(stages) + 1 # +1 for final synthesis
yield sse({"type": "status", "message": f"Pipeline: {''.join(stages)} → SYNTHESIZE"})
# Stage 2: Execute each refinement stage
current_content = prompt
stage_outputs = {}
stage_prompts = {
"VALIDATE": "You are a rigorous fact-checker. Review this {type} and identify:\n1. Claims that need evidence or citations\n2. Statements that may be inaccurate\n3. Assumptions presented as facts\n4. Numbers or statistics that seem off\n\nFor each issue, explain WHY it's a concern and suggest a fix.\n\nCONTENT:\n{content}",
"CRITIQUE": "You are a senior reviewer who has seen hundreds of {type}s. Give a brutally honest critique:\n1. What's weak or underdeveloped?\n2. What's missing entirely?\n3. What contradicts itself?\n4. What would a skeptical reader push back on?\n5. What's the single biggest improvement this needs?\n\nBe specific — quote the parts you're criticizing.\n\nCONTENT:\n{content}",
"EXPAND": "You are a domain expert deepening a {type}. Based on the critique below, expand the weak areas:\n\nPREVIOUS CRITIQUE:\n{prev}\n\nORIGINAL CONTENT:\n{content}\n\nFor each gap or weakness identified, write the missing content that should be added. Be substantive — don't just say 'add more detail', actually write the detail.",
"STRUCTURE": "You are an editor restructuring a {type} for maximum clarity and impact. Reorganize this content:\n1. Improve the logical flow — put dependencies before dependents\n2. Add clear section headings if missing\n3. Move related ideas together\n4. Ensure the opening sets up what follows\n5. Ensure the conclusion delivers on the opening's promise\n\nReturn the FULL restructured document, not just suggestions.\n\nCONTENT:\n{content}",
"STAKEHOLDER": "You are conducting a stakeholder analysis of this {type}. Analyze from these perspectives:\n1. END USER: Does this serve their needs? What's missing from their view?\n2. DEVELOPER/IMPLEMENTER: Is this buildable? What's ambiguous?\n3. BUSINESS/LEADERSHIP: Does this align with business goals? ROI clear?\n4. OPERATIONS: Deployment, maintenance, scaling concerns?\n\nFor each perspective, give specific feedback with quotes from the content.\n\nCONTENT:\n{content}",
"CLARITY": "You are a clarity editor. Improve the readability of this {type}:\n1. Replace jargon with plain language (or define it on first use)\n2. Break long sentences into shorter ones\n3. Remove filler words and redundancy\n4. Sharpen vague language into concrete specifics\n5. Ensure consistent terminology throughout\n\nReturn the FULL improved document.\n\nCONTENT:\n{content}",
"EDGE_CASES": "You are a risk analyst reviewing this {type}. Identify:\n1. Edge cases not addressed\n2. Failure modes not considered\n3. 'What if X goes wrong?' scenarios\n4. Scale concerns (what breaks at 10x, 100x?)\n5. Security, privacy, or compliance gaps\n6. Dependencies that could fail\n\nFor each risk, rate severity (low/medium/high) and suggest mitigation.\n\nCONTENT:\n{content}",
"ALIGN": "You are checking alignment between intent and execution in this {type}.\n1. What does the opening promise?\n2. Does the body deliver on every promise?\n3. Are there sections that drift from the stated goal?\n4. Is the scope consistent throughout?\n5. Does the conclusion match the introduction?\n\nQuote specific misalignments and suggest fixes.\n\nCONTENT:\n{content}",
}
prev_output = ""
for si, stage in enumerate(stages):
stage_num = si + 1
worker = workers[si % len(workers)]
yield sse({"type": "progress", "current": stage_num, "total": total_stages, "label": stage.lower()})
yield sse({"type": "status", "message": f"Stage {stage_num}/{total_stages}: {stage} ({worker})..."})
template = stage_prompts.get(stage, "Analyze and improve this {type}:\n\n{content}")
stage_prompt = template.format(
type=content_type,
content=cap_response(current_content)[:6000],
prev=cap_response(prev_output)[:3000] if prev_output else "(none)"
)
try:
result = safe_query(worker, stage_prompt)
yield sse({"type": "response", "model": worker, "text": result, "role": stage.lower()})
stage_outputs[stage] = result
prev_output = result
steps.append({"step": stage, "model": worker, "output": result[:1000]})
# For STRUCTURE and CLARITY stages, the output replaces the working content
if stage in ("STRUCTURE", "CLARITY"):
current_content = result
except Exception as e:
yield sse({"type": "response", "model": worker, "text": f"{stage} failed: {e}", "role": "error"})
stage_outputs[stage] = f"Error: {e}"
# Stage 3: Final synthesis — combine all insights into the definitive refined version
yield sse({"type": "progress", "current": total_stages, "total": total_stages, "label": "synthesize"})
yield sse({"type": "status", "message": f"Final synthesis with {orchestrator}..."})
insights_text = ""
for stage, output in stage_outputs.items():
insights_text += f"\n{'='*40}\n{stage} FINDINGS:\n{'='*40}\n{output[:2500]}\n"
synth_prompt = f"""You are producing the FINAL refined version of a {content_type}.
ORIGINAL CONTENT:
{cap_response(prompt)[:5000]}
REFINEMENT INSIGHTS:
{cap_response(insights_text)[:8000]}
Your task: produce the COMPLETE, FINAL, refined version of this {content_type}. This is not a summary of changes this IS the improved document. Incorporate every valid insight from the refinement stages. The output should be ready to use as-is.
Rules:
- Keep the original author's voice and intent
- Integrate improvements seamlessly, don't annotate them
- If a stage found issues, fix them in the text
- If a stage suggested additions, add them in the right place
- The result should read as if a senior expert wrote it from scratch
- Output the FULL document, not a diff or summary"""
try:
final = safe_query(orchestrator, synth_prompt)
yield sse({"type": "response", "model": orchestrator, "text": final, "role": "final", "highlight": True})
steps.append({"step": "synthesis", "model": orchestrator, "output": final[:2000]})
except Exception as e:
yield sse({"type": "response", "model": orchestrator, "text": f"Synthesis failed: {e}", "role": "error"})
result_data = {
"content_type": content_type,
"stages_run": stages,
"stage_count": len(stages),
"plan": plan,
}
_save_pipeline("refine", prompt[:200], steps, result_data, all_models, start)
# ─── AI SECURITY SENTINEL ─────────────────────────────────────
SENTINEL_LOG = "/var/log/llm-team-sentinel.log"