1717 -webkit-font-smoothing : antialiased;
1818}
1919
20+ /* ── Light Theme ── */
21+ body .light {
22+ --bg-primary : # ffffff ;
23+ --bg-card : # f3f4f6 ;
24+ --text-primary : # 0f172a ;
25+ --text-secondary : # 475569 ;
26+ --text-tertiary : # 94a3b8 ;
27+ --border-subtle : # e2e8f0 ;
28+ --accent : # 2563eb ;
29+ --accent-subtle : rgba (37 , 99 , 235 , 0.08 );
30+ --accent-glow : rgba (37 , 99 , 235 , 0.04 );
31+ }
32+
2033/* ── App Shell ── */
2134# app {
2235 display : grid;
89102.session-item .del-btn { margin-left : auto; opacity : 0 ; transition : opacity 0.15s ; color : var (--text-tertiary ); cursor : pointer; font-size : 12px ; padding : 0 4px ; }
90103.session-item : hover .del-btn { opacity : 0.6 ; }
91104.session-item .del-btn : hover { opacity : 1 ; color : # ef4444 ; }
105+ .session-item .rename-btn { opacity : 0 ; transition : opacity 0.15s ; color : var (--text-tertiary ); cursor : pointer; font-size : 12px ; padding : 0 4px ; }
106+ .session-item : hover .rename-btn { opacity : 0.6 ; }
107+ .session-item .rename-btn : hover { opacity : 1 ; color : var (--accent ); }
92108
93109/* ── Main Chat ── */
94110# main {
11231139}
11241140# cancel-btn svg { width : 12px ; height : 12px ; flex-shrink : 0 ; }
11251141
1142+ /* ── Theme Toggle & Model Picker ── */
1143+ # theme-btn {
1144+ background : transparent; border : 1px solid var (--border-subtle ); color : var (--text-secondary );
1145+ cursor : pointer; font-size : 15px ; padding : 4px 6px ; border-radius : 6px ;
1146+ transition : all .15s ; line-height : 1 ; flex-shrink : 0 ; display : flex; align-items : center;
1147+ }
1148+ # theme-btn : hover { color : var (--accent ); background : rgba (56 , 189 , 248 , 0.06 ); }
1149+ # model-picker {
1150+ background : var (--bg-card ); color : var (--text-secondary ); border : 1px solid var (--border-subtle );
1151+ border-radius : 6px ; padding : 2px 6px ; font-size : 11px ; font-family : var (--font-mono );
1152+ cursor : pointer; outline : none; transition : border-color .15s ; max-width : 140px ;
1153+ }
1154+ # model-picker : hover { border-color : var (--accent ); }
1155+ # model-picker : focus { border-color : var (--accent ); }
1156+ # model-picker optgroup , # model-picker option { background : var (--bg-card ); color : var (--text-primary ); }
1157+
11261158/* ── Phase 1: Scroll-to-Bottom Button ── */
11271159# scroll-bottom-btn {
11281160 display : none; position : absolute; bottom : 20px ; right : 16px ;
12071239 < div class ="stats " id ="stats-bar "> </ div >
12081240 < span class ="model " id ="model-label "> </ span >
12091241 < span class ="session-stats " id ="session-stats "> </ span >
1242+ < select id ="model-picker " onchange ="switchModel(this.value) " title ="Switch model ">
1243+ < option value =""> Loading...</ option >
1244+ </ select >
1245+ < button id ="theme-btn " onclick ="toggleTheme() " title ="Toggle theme " aria-label ="Toggle theme "> 🌙</ button >
12101246 < button id ="cancel-btn " onclick ="cancelAgent() " aria-label ="Cancel ">
12111247 < svg viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2.5 " stroke-linecap ="round "> < line x1 ="18 " y1 ="6 " x2 ="6 " y2 ="18 "/> < line x1 ="6 " y1 ="6 " x2 ="18 " y2 ="18 "/> </ svg >
12121248 </ button >
@@ -1331,6 +1367,8 @@ <h3>Delete session?</h3>
13311367let history = JSON . parse ( localStorage . getItem ( 'kode_history' ) || '[]' ) ;
13321368let historyIdx = - 1 ;
13331369let attachedFiles = [ ] ; // {name, size, content}
1370+ let currentModel = localStorage . getItem ( 'kode_model' ) || '' ;
1371+ let availableModels = [ ] ;
13341372
13351373// ── DOM ──
13361374const messagesEl = document . getElementById ( 'messages' ) ;
@@ -1518,6 +1556,12 @@ <h3>Delete session?</h3>
15181556 if ( event . model ) {
15191557 statsBar . textContent = event . model ;
15201558 }
1559+ // Sync model picker with server-confirmed model
1560+ if ( event . model && currentModel !== event . model ) {
1561+ currentModel = event . model ;
1562+ const picker = document . getElementById ( 'model-picker' ) ;
1563+ if ( picker ) picker . value = event . model ;
1564+ }
15211565 loadSessions ( ) ;
15221566 break ;
15231567
@@ -1721,6 +1765,12 @@ <h3>Delete session?</h3>
17211765
17221766 streamBubbleEl = wrapper ;
17231767 streamContentEl = wrapper . querySelector ( '#stream-content' ) ;
1768+ // Add copy button and collapse check to the stream bubble
1769+ const bubble = wrapper . querySelector ( '.bubble' ) ;
1770+ if ( bubble ) {
1771+ addCopyButton ( bubble ) ;
1772+ checkCollapse ( bubble ) ;
1773+ }
17241774 pruneMessages ( ) ;
17251775 scrollBottom ( ) ;
17261776}
@@ -2050,6 +2100,78 @@ <h3>Delete session?</h3>
20502100 . catch ( ( ) => showToast ( 'Failed to delete session' ) ) ;
20512101} ;
20522102
2103+ // ── Theme Toggle ──
2104+ window . toggleTheme = function ( ) {
2105+ document . body . classList . toggle ( 'light' ) ;
2106+ const isLight = document . body . classList . contains ( 'light' ) ;
2107+ localStorage . setItem ( 'kode_theme' , isLight ? 'light' : 'dark' ) ;
2108+ document . getElementById ( 'theme-btn' ) . textContent = isLight ? '☀️' : '🌙' ;
2109+ } ;
2110+ // Restore theme on load
2111+ if ( localStorage . getItem ( 'kode_theme' ) === 'light' ) {
2112+ document . body . classList . add ( 'light' ) ;
2113+ document . getElementById ( 'theme-btn' ) . textContent = '☀️' ;
2114+ }
2115+
2116+ // ── Model Picker ──
2117+ async function fetchModels ( ) {
2118+ const picker = document . getElementById ( 'model-picker' ) ;
2119+ try {
2120+ picker . disabled = true ;
2121+ const resp = await fetch ( '/api/models' ) ;
2122+ if ( ! resp . ok ) { picker . innerHTML = '<option value="">Models unavailable</option>' ; return ; }
2123+ const models = await resp . json ( ) ;
2124+ availableModels = models ;
2125+ if ( ! models || models . length === 0 ) {
2126+ picker . innerHTML = '<option value="">No models</option>' ;
2127+ return ;
2128+ }
2129+ // Build <option> list, group by top-level prefix
2130+ let html = '<option value="">— select model —</option>' ;
2131+ models . forEach ( m => {
2132+ const sel = currentModel === m . id ? ' selected' : '' ;
2133+ html += `<option value="${ escapeAttr ( m . id ) } "${ sel } >${ escapeHtml ( m . description || m . id ) } </option>` ;
2134+ } ) ;
2135+ picker . innerHTML = html ;
2136+ if ( currentModel ) picker . value = currentModel ;
2137+ } catch {
2138+ picker . innerHTML = '<option value="">Failed to load</option>' ;
2139+ } finally {
2140+ picker . disabled = false ;
2141+ }
2142+ }
2143+
2144+ window . switchModel = function ( modelId ) {
2145+ currentModel = modelId ;
2146+ if ( modelId ) {
2147+ localStorage . setItem ( 'kode_model' , modelId ) ;
2148+ } else {
2149+ localStorage . removeItem ( 'kode_model' ) ;
2150+ }
2151+ showToast ( modelId ? 'Model: ' + modelId : 'Using default model' ) ;
2152+ } ;
2153+
2154+ // ── Session Rename ──
2155+ window . renameSession = function ( sid , el ) {
2156+ const item = el . closest ( '.session-item' ) ;
2157+ if ( ! item ) return ;
2158+ const taskEl = item . querySelector ( '.task' ) ;
2159+ const currentName = taskEl ? taskEl . textContent : '' ;
2160+ const newName = prompt ( 'Rename session:' , currentName ) ;
2161+ if ( ! newName || newName === currentName ) return ;
2162+ fetch ( '/api/sessions/' + encodeURIComponent ( sid ) , {
2163+ method : 'POST' ,
2164+ headers : { 'Content-Type' : 'application/json' } ,
2165+ body : JSON . stringify ( { name : newName } )
2166+ } )
2167+ . then ( resp => {
2168+ if ( ! resp . ok ) throw new Error ( 'rename failed' ) ;
2169+ loadSessions ( ) ;
2170+ showToast ( 'Session renamed' ) ;
2171+ } )
2172+ . catch ( ( ) => showToast ( 'Failed to rename session' ) ) ;
2173+ } ;
2174+
20532175// ── Shortcuts ──
20542176window . toggleShortcuts = function ( ) {
20552177 document . getElementById ( 'shortcuts-overlay' ) . classList . toggle ( 'active' ) ;
@@ -2213,7 +2335,8 @@ <h3>Delete session?</h3>
22132335 ws . send ( JSON . stringify ( {
22142336 type : 'prompt' ,
22152337 content : text ,
2216- session_id : sessionId
2338+ session_id : sessionId ,
2339+ model : currentModel || undefined
22172340 } ) ) ;
22182341}
22192342
@@ -2523,6 +2646,7 @@ <h3>Delete session?</h3>
25232646 `<div class="session-item${ s . id === sessionId ? ' active' : '' } " data-id="${ escapeAttr ( s . id ) } ">
25242647 <div style="display:flex;align-items:center;gap:4px">
25252648 <div class="id">${ escapeHtml ( s . id . slice ( 0 , 8 ) ) } </div>
2649+ <span class="rename-btn" title="Rename" onclick="event.stopPropagation();renameSession('${ escapeAttr ( s . id ) } ', this)">✎</span>
25262650 <span class="del-btn" title="Delete">✕</span>
25272651 </div>
25282652 <div class="task">${ escapeHtml ( s . task || '' ) } </div>
@@ -2540,6 +2664,7 @@ <h3>Delete session?</h3>
25402664// Show skeleton while connecting
25412665if ( skeletonEl ) skeletonEl . classList . add ( 'visible' ) ;
25422666loadSessions ( ) ;
2667+ fetchModels ( ) ;
25432668promptEl . focus ( ) ;
25442669
25452670// Handle keyboard shortcuts globally
0 commit comments