@@ -83,6 +83,7 @@ <h1 style="margin:0; font-size: 1rem; letter-spacing: -0.025em;">PROMPT<span sty
8383 < div class ="nav-item " onclick ="switchView('intelligence') "> 🧠 COMMIT INTELLIGENCE</ div >
8484 < div class ="nav-item " onclick ="switchView('learning') "> 📖 LEARNING LAYER</ div >
8585 < div class ="nav-item " onclick ="switchView('library') "> 📚 PROMPT LIBRARY</ div >
86+ < div class ="nav-item " onclick ="switchView('tournaments') "> 🏆 TOURNAMENTS</ div >
8687 < div class ="nav-item " onclick ="switchView('health') "> PROVIDER HEALTH</ div >
8788
8889 < div style ="margin-top: 2rem; font-size: 0.6rem; color: var(--dim); text-transform: uppercase; letter-spacing: 0.1em; padding-left: 0.75rem; "> Selected Project</ div >
@@ -168,6 +169,35 @@ <h2>Provider Metrics</h2>
168169 </ div >
169170 </ div >
170171 </ div >
172+
173+ <!-- VIEW: TOURNAMENTS -->
174+ < div id ="view-tournaments " class ="main-view hidden ">
175+ < div class ="grid ">
176+ < div class ="card col-12 ">
177+ < h2 > Run A/B Prompt Tournament</ h2 >
178+ < div style ="display: flex; gap: 1rem; margin-bottom: 1rem; ">
179+ < div style ="flex: 1; ">
180+ < label style ="display:block; margin-bottom: 4px; color: var(--dim); "> Baseline Prompt</ label >
181+ < textarea id ="t-baseline " style ="width: 100%; height: 100px; background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); color: var(--text); padding: 8px; font-family: monospace; "> </ textarea >
182+ </ div >
183+ < div style ="flex: 1; ">
184+ < label style ="display:block; margin-bottom: 4px; color: var(--dim); "> Variant A</ label >
185+ < textarea id ="t-variant-a " style ="width: 100%; height: 100px; background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); color: var(--text); padding: 8px; font-family: monospace; "> </ textarea >
186+ </ div >
187+ < div style ="flex: 1; ">
188+ < label style ="display:block; margin-bottom: 4px; color: var(--dim); "> Variant B</ label >
189+ < textarea id ="t-variant-b " style ="width: 100%; height: 100px; background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); color: var(--text); padding: 8px; font-family: monospace; "> </ textarea >
190+ </ div >
191+ </ div >
192+ < button onclick ="runTournament() " style ="background: var(--accent); color: #fff; border: none; padding: 8px 16px; cursor: pointer; border-radius: 4px; font-weight: bold; "> Run Heuristic Tournament</ button >
193+ < div id ="tournament-run-status " style ="margin-top: 8px; font-size: 0.8rem; color: var(--dim); "> </ div >
194+ </ div >
195+ < div class ="card col-12 ">
196+ < h2 > Tournament History</ h2 >
197+ < div id ="tournament-history "> </ div >
198+ </ div >
199+ </ div >
200+ </ div >
171201 </ div >
172202
173203 < script id ="dashboard-data " type ="application/json "> { { STATE_JSON } } </ script >
@@ -190,12 +220,38 @@ <h2>Provider Metrics</h2>
190220 document . querySelectorAll ( '.nav-item' ) . forEach ( n => n . classList . remove ( 'active' ) ) ;
191221 event . target . classList . add ( 'active' ) ;
192222
193- const titles = { 'stream' : 'Global Intelligence Stream' , 'intelligence' : 'Commit Intelligence' , 'learning' : 'Learning Layer' , 'library' : 'Prompt Library' , 'health' : 'Provider Health' } ;
223+ const titles = { 'stream' : 'Global Intelligence Stream' , 'intelligence' : 'Commit Intelligence' , 'learning' : 'Learning Layer' , 'library' : 'Prompt Library' , 'tournaments' : 'A/B Tournaments' , ' health' : 'Provider Health' } ;
194224 document . getElementById ( 'view-title' ) . textContent = titles [ viewId ] ;
195225 currentView = viewId ;
196226 refreshData ( ) ;
197227 }
198228
229+ async function runTournament ( ) {
230+ const baseline = document . getElementById ( 't-baseline' ) . value ;
231+ const variantA = document . getElementById ( 't-variant-a' ) . value ;
232+ const variantB = document . getElementById ( 't-variant-b' ) . value ;
233+ if ( ! baseline || ! variantA || ! variantB ) {
234+ document . getElementById ( 'tournament-run-status' ) . textContent = "All three prompts are required." ;
235+ return ;
236+ }
237+ document . getElementById ( 'tournament-run-status' ) . textContent = "Running heuristic evaluation..." ;
238+ try {
239+ const res = await fetch ( `/api/tournaments/run?project=${ encodeURIComponent ( currentProject ) } ` , {
240+ method : 'POST' ,
241+ headers : { 'Content-Type' : 'application/json' } ,
242+ body : JSON . stringify ( { baseline, variantA, variantB } )
243+ } ) ;
244+ if ( res . ok ) {
245+ document . getElementById ( 'tournament-run-status' ) . innerHTML = `<span style="color:#22c55e">Tournament complete!</span>` ;
246+ refreshData ( ) ;
247+ } else {
248+ document . getElementById ( 'tournament-run-status' ) . textContent = "Failed to run tournament." ;
249+ }
250+ } catch ( e ) {
251+ document . getElementById ( 'tournament-run-status' ) . textContent = "Error: " + e . message ;
252+ }
253+ }
254+
199255 async function refreshData ( ) {
200256 // 1. Refresh Basic State
201257 const stateRes = await fetch ( `/api/state?project=${ encodeURIComponent ( currentProject ) } ` ) ;
@@ -313,6 +369,46 @@ <h2>Provider Metrics</h2>
313369 ` ) . join ( '' ) || '<p style="color:var(--dim)">Prompt library is currently empty.</p>' ;
314370 }
315371
372+ if ( currentView === 'tournaments' ) {
373+ const res = await fetch ( `/api/tournaments?project=${ encodeURIComponent ( currentProject ) } ` ) ;
374+ const data = await res . json ( ) ;
375+ document . getElementById ( 'tournament-history' ) . innerHTML = data . map ( t => {
376+ let details = { } ;
377+ try { details = JSON . parse ( t . details_json ) ; } catch ( e ) { }
378+
379+ let winnerHtml = `<span class="badge" style="background: rgba(34, 197, 94, 0.15); color: #86efac; border-color: rgba(34, 197, 94, 0.3);">WINNER: ${ t . winner_observed } </span>` ;
380+ if ( t . winner_observed === 'tie' ) {
381+ winnerHtml = `<span class="badge" style="background: rgba(255, 255, 255, 0.15); color: #fff; border-color: rgba(255, 255, 255, 0.3);">TIE</span>` ;
382+ }
383+
384+ let scoreA = details . variantA ?. evaluation ?. heuristicScore || 0 ;
385+ let scoreB = details . variantB ?. evaluation ?. heuristicScore || 0 ;
386+
387+ return `
388+ <div class="card" style="border-top: 2px solid ${ t . winner_observed === 'A' ? 'var(--accent)' : t . winner_observed === 'B' ? '#f472b6' : 'var(--dim)' } ; margin-bottom: 1rem;">
389+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 8px;">
390+ <strong>Tournament ${ t . id . substring ( 0 , 8 ) } ...</strong>
391+ ${ winnerHtml }
392+ </div>
393+ <div style="font-size: 0.8rem; color: var(--dim); margin-bottom: 8px;">Created: ${ new Date ( t . created_at ) . toLocaleString ( ) } </div>
394+
395+ <div style="font-size: 0.85rem; color: var(--dim); margin-bottom: 4px;"><strong>BASELINE:</strong> ${ escapeHtml ( t . baseline_prompt ) } </div>
396+
397+ <div style="display: flex; gap: 1rem; margin-top: 10px;">
398+ <div style="flex:1; padding: 10px; background: rgba(0,0,0,0.2); border-left: 2px solid var(--accent); border-radius: 4px;">
399+ <div style="margin-bottom: 6px;"><strong>VARIANT A</strong> (Score: ${ scoreA } )</div>
400+ <pre style="font-size: 0.75rem; white-space: pre-wrap;">${ escapeHtml ( t . variant_a ) } </pre>
401+ </div>
402+ <div style="flex:1; padding: 10px; background: rgba(0,0,0,0.2); border-left: 2px solid #f472b6; border-radius: 4px;">
403+ <div style="margin-bottom: 6px;"><strong>VARIANT B</strong> (Score: ${ scoreB } )</div>
404+ <pre style="font-size: 0.75rem; white-space: pre-wrap;">${ escapeHtml ( t . variant_b ) } </pre>
405+ </div>
406+ </div>
407+ </div>
408+ ` ;
409+ } ) . join ( '' ) || '<p style="color:var(--dim)">No tournament history found.</p>' ;
410+ }
411+
316412 if ( currentView === 'health' ) {
317413 const res = await fetch ( `/api/health?project=${ encodeURIComponent ( currentProject ) } ` ) ;
318414 const health = await res . json ( ) ;
0 commit comments