@@ -1746,7 +1746,18 @@ function beginComboEdit(combo) {
17461746 model : member . model || member . upstreamModel ,
17471747 } ) ) ,
17481748 } ;
1749- renderCombos ( ) ;
1749+ renderCombos ( { focusEditor : true } ) ;
1750+ }
1751+
1752+ function focusRouteEditor ( ) {
1753+ const editorEl = view . querySelector ( ".route-editor" ) ;
1754+ if ( ! editorEl ) return ;
1755+ editorEl . scrollIntoView ( { block : "start" , behavior : "auto" } ) ;
1756+ const name = $ ( "#c-name" ) ;
1757+ if ( name ) {
1758+ name . focus ( { preventScroll : true } ) ;
1759+ if ( typeof name . select === "function" ) name . select ( ) ;
1760+ }
17501761}
17511762
17521763function syncComboDraft ( ) {
@@ -1767,7 +1778,7 @@ function routeAccountLabel(account) {
17671778 return account . name ;
17681779}
17691780
1770- function renderCombos ( ) {
1781+ function renderCombos ( options = { } ) {
17711782 if ( blockSensitiveRenderIfLocked ( ) ) return ;
17721783 const combos = state . combos || [ ] ;
17731784 const models = flatModels ( ) ;
@@ -1787,15 +1798,14 @@ function renderCombos() {
17871798 editor . pickerModelId = null ;
17881799 }
17891800
1790- view . innerHTML = `
1791- ${ pageHeader ( "Routing" , "Routes" , "Give clients one memorable model ID while ReRouted handles account and provider failover." , '<button type="button" class="btn btn-primary btn-sm" id="btn-new-route">New route</button>' ) }
1801+ const listMarkup = `
17921802 <div class="route-card-grid">
17931803 ${
17941804 combos . length
17951805 ? combos
17961806 . map (
17971807 ( c , index ) => `
1798- <article class="route-card">
1808+ <article class="route-card${ editor && ( editor . id === ( c . storageId || c . id ) || ( ! editor . id && ! c . storageId && ! c . id ) ) ? " is-active" : "" } ">
17991809 <button type="button" class="route-card-hit" data-edit-index="${ index } " aria-label="Edit route ${ esc ( comboRouteId ( c ) ) } "></button>
18001810 <div class="route-card-top">
18011811 <span class="strategy-badge">${ c . strategy === "round-robin" ? "Round robin" : "Fallback" } </span>
@@ -1804,17 +1814,21 @@ function renderCombos() {
18041814 <div class="route-name">${ esc ( comboRouteId ( c ) ) } </div>
18051815 <div class="route-summary">${ c . strategy === "round-robin" ? "Rotates every request" : "Fills in order" } · ${ ( c . members || [ ] ) . length } member${ ( c . members || [ ] ) . length === 1 ? "" : "s" } </div>
18061816 <div class="route-nodes" aria-hidden="true">${ ( c . members || [ ] ) . slice ( 0 , 5 ) . map ( ( _ , index ) => `${ index ? '<span class="route-node-line"></span>' : "" } <span class="route-node">${ index + 1 } </span>` ) . join ( "" ) } ${ ( c . members || [ ] ) . length > 5 ? '<span class="route-node-line"></span><span class="route-node">+</span>' : "" } </div>
1807- <div class="route-card-action" aria-hidden="true">Edit route <span>→</span></div>
1817+ <div class="route-card-action" aria-hidden="true">${ editor && ( editor . id === ( c . storageId || c . id ) ) ? "Editing" : "Open" } <span>→</span></div>
18081818 </article>`
18091819 )
18101820 . join ( "" )
18111821 : `<div class="empty">No routes yet. Create a memorable model ID and choose where requests should go.</div>`
18121822 }
1813- </div>
1814- ${
1815- editor
1816- ? `<section class="action-panel route-editor">
1817- <div class="action-panel-head"><div class="eyebrow">${ editor . id ? "Edit route" : "New route" } </div><div class="action-panel-title">${ editor . id ? esc ( editor . name || "Route" ) : "Build a route" } </div><div class="action-panel-sub">The model ID below is what clients see in <span class="mono">/v1/models</span>.</div></div>
1823+ </div>` ;
1824+
1825+ const editorMarkup = editor
1826+ ? `<section class="action-panel route-editor" id="route-editor">
1827+ <div class="action-panel-head">
1828+ <div class="eyebrow">${ editor . id ? "Edit route" : "New route" } </div>
1829+ <div class="action-panel-title">${ editor . id ? esc ( editor . name || "Route" ) : "Build a route" } </div>
1830+ <div class="action-panel-sub">The model ID below is what clients see in <span class="mono">/v1/models</span>.</div>
1831+ </div>
18181832 <div class="label">Model ID</div>
18191833 <input class="input" id="c-name" placeholder="coding-fast" value="${ esc ( editor . name ) } " />
18201834 <div class="label">Routing behavior</div>
@@ -1845,17 +1859,39 @@ function renderCombos() {
18451859 <label class="route-picker-field" for="c-add-model"><span class="label">Model</span><select class="select" id="c-add-model" ${ editor . pickerAccountId ? "" : "disabled" } ><option value="">${ editor . pickerAccountId ? "Choose a model…" : "Choose an account first…" } </option>${ pickerModels . map ( ( model ) => `<option value="${ esc ( model . upstreamModel ) } " ${ model . upstreamModel === editor . pickerModelId ? "selected" : "" } >${ esc ( model . name ) } · ${ esc ( model . upstreamModel ) } </option>` ) . join ( "" ) } </select></label>
18461860 <button type="button" class="btn btn-secondary btn-sm route-picker-add" id="btn-add-member" ${ editor . pickerAccountId && editor . pickerModelId ? "" : "disabled" } >Add to route</button>
18471861 </div>
1848- <div class="btn-row"><button type="button" class="btn btn-secondary" id="btn-cancel-edit">Cancel </button><button type="button" class="btn btn-primary" id="btn-create">${ editor . id ? "Save route" : "Create route" } </button></div>
1862+ <div class="btn-row"><button type="button" class="btn btn-secondary" id="btn-cancel-edit">Back to routes </button><button type="button" class="btn btn-primary" id="btn-create">${ editor . id ? "Save route" : "Create route" } </button></div>
18491863 </section>`
1850- : ""
1851- }
1864+ : "" ;
1865+
1866+ // When editing, replace the list with the editor (full page take-over). No buried bottom panel.
1867+ view . innerHTML = `
1868+ ${ pageHeader (
1869+ "Routing" ,
1870+ editor ? ( editor . id ? editor . name || "Edit route" : "New route" ) : "Routes" ,
1871+ editor
1872+ ? "Change the model ID, order, and failover for this route."
1873+ : "Give clients one memorable model ID while ReRouted handles account and provider failover." ,
1874+ editor
1875+ ? '<button type="button" class="btn btn-secondary btn-sm" id="btn-back-routes">← Routes</button>'
1876+ : '<button type="button" class="btn btn-primary btn-sm" id="btn-new-route">New route</button>'
1877+ ) }
1878+ ${ editor ? editorMarkup : listMarkup }
18521879 ` ;
1853- $ ( "#btn-new-route" ) . onclick = ( ) => beginComboEdit ( null ) ;
1880+
1881+ const backToList = ( ) => {
1882+ comboDraft = null ;
1883+ renderCombos ( ) ;
1884+ } ;
1885+ const backBtn = $ ( "#btn-back-routes" ) ;
1886+ if ( backBtn ) backBtn . onclick = backToList ;
1887+ const newBtn = $ ( "#btn-new-route" ) ;
1888+ if ( newBtn ) newBtn . onclick = ( ) => beginComboEdit ( null ) ;
18541889 view . querySelectorAll ( "button[data-edit-index]" ) . forEach ( ( btn ) => {
18551890 btn . onclick = ( ) => beginComboEdit ( combos [ Number ( btn . dataset . editIndex ) ] ) ;
18561891 } ) ;
18571892 view . querySelectorAll ( "button[data-del-index]" ) . forEach ( ( btn ) => {
1858- btn . onclick = async ( ) => {
1893+ btn . onclick = async ( event ) => {
1894+ event . stopPropagation ( ) ;
18591895 const combo = combos [ Number ( btn . dataset . delIndex ) ] ;
18601896 const id = combo ?. storageId || combo ?. id ;
18611897 if ( ! id ) return ;
@@ -1984,6 +2020,9 @@ function renderCombos() {
19842020 await refresh ( ) ;
19852021 renderCombos ( ) ;
19862022 } ;
2023+ if ( options . focusEditor ) {
2024+ requestAnimationFrame ( ( ) => focusRouteEditor ( ) ) ;
2025+ }
19872026}
19882027
19892028let statsPeriod = "24h" ;
0 commit comments