Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/main/mobile/lan-mobile-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,13 @@ function renderMessage(message){const blocks=message.blocks.filter(Boolean);retu
function presetIsLocked(){return!sessionBlank||optimisticPrompts.length>0}
function closeSessionSettings(){$('sessionSettings').hidden=true;$('sessionSettings').innerHTML='';$('settings').setAttribute('aria-expanded','false')}
function selectedModelEntry(){const value=$('modelSelect')?.value||'',parts=value.split('\u001f'),group=(modelCatalog?.groups||[]).find(item=>item.id===parts[0]);return{group,model:group?.models?.find(item=>item.id===parts[1])}}
function syncEffortOptions(){const field=$('effortField'),select=$('effortSelect'),entry=selectedModelEntry().model,efforts=entry?.reasoning?.efforts||[];if(!field||!select)return;field.hidden=!efforts.length;if(!efforts.length){select.innerHTML='';return}const previous=select.value,current=modelCatalog?.current?.reasoningEffort,preferred=efforts.some(item=>item.id===previous)?previous:efforts.some(item=>item.id===current)?current:entry.reasoning.defaultEffort||efforts[0].id;select.innerHTML=efforts.map(item=>'<option value="'+esc(item.id)+'">'+esc(item.name)+'</option>').join('');select.value=preferred}
function syncSettingsControls(){const locked=presetIsLocked(),preset=$('presetSelect'),note=$('presetNote');if(preset)preset.disabled=settingsBusy||locked;if(note)note.textContent=locked?L.presetLocked:L.presetEditable;const model=$('modelSelect'),effort=$('effortSelect');if(model)model.disabled=settingsBusy||!modelCatalog?.routable;if(effort)effort.disabled=settingsBusy||!modelCatalog?.routable}
function renderSessionSettings(){const host=$('sessionSettings');if(!modelCatalog){host.innerHTML='<div class="settings-head"><strong>'+L.sessionSettings+'</strong><button class="settings-close" data-settings-close aria-label="Close">×</button></div><div class="settings-loading">'+L.settingsLoading+'</div>';return}const current=modelCatalog.current||{},modelValue=current.provider+'\u001f'+current.model,presetFallback=presets.find(item=>item.isDefault)?.id||presets[0]?.id||'',presetValue=presets.some(item=>item.id===currentPreset)?currentPreset:presetFallback;host.innerHTML='<div class="settings-head"><strong>'+L.sessionSettings+'</strong><button class="settings-close" data-settings-close aria-label="Close">×</button></div><label class="setting-field"><span>'+L.model+'</span><select id="modelSelect"'+(!modelCatalog.groups?.length?' disabled':'')+'>'+(modelCatalog.groups||[]).map(group=>'<optgroup label="'+esc(group.name)+'">'+(group.models||[]).map(model=>'<option value="'+esc(group.id+'\u001f'+model.id)+'">'+esc(model.name)+'</option>').join('')+'</optgroup>').join('')+'</select></label><label id="effortField" class="setting-field" hidden><span>'+L.reasoningEffort+'</span><select id="effortSelect"></select></label><label class="setting-field"><span>'+L.preset+'</span><select id="presetSelect">'+presets.map(item=>'<option value="'+esc(item.id)+'">'+esc(presetLabel(item))+'</option>').join('')+'</select></label><div id="presetNote" class="setting-note"></div><div id="settingsError" class="settings-error">'+(!modelCatalog.groups?.length?L.noModels:'')+'</div>';if((modelCatalog.groups||[]).some(group=>(group.models||[]).some(model=>group.id+'\u001f'+model.id===modelValue)))$('modelSelect').value=modelValue;if($('presetSelect'))$('presetSelect').value=presetValue;syncEffortOptions();syncSettingsControls()}
function syncEffortOptions(){const field=$('effortField'),select=$('effortSelect'),entry=selectedModelEntry().model,efforts=entry?.reasoning?.efforts||[];if(!field||!select)return;field.hidden=!efforts.length;if(!efforts.length){select.innerHTML='';return}const previous=select.value,current=(modelCatalog?.current??modelCatalog?.default)?.reasoningEffort,preferred=efforts.some(item=>item.id===previous)?previous:efforts.some(item=>item.id===current)?current:entry.reasoning.defaultEffort||efforts[0].id;select.innerHTML=efforts.map(item=>'<option value="'+esc(item.id)+'">'+esc(item.name)+'</option>').join('');select.value=preferred}
function syncSettingsControls(){const locked=presetIsLocked(),preset=$('presetSelect'),note=$('presetNote');if(preset)preset.disabled=settingsBusy||locked;if(note)note.textContent=locked?L.presetLocked:L.presetEditable;const model=$('modelSelect'),effort=$('effortSelect'),routable=(modelCatalog?.routableProviders?.length??0)>0||modelCatalog?.routable===true;if(model)model.disabled=settingsBusy||!routable;if(effort)effort.disabled=settingsBusy||!routable}
function renderSessionSettings(){const host=$('sessionSettings');if(!modelCatalog){host.innerHTML='<div class="settings-head"><strong>'+L.sessionSettings+'</strong><button class="settings-close" data-settings-close aria-label="Close">×</button></div><div class="settings-loading">'+L.settingsLoading+'</div>';return}const current=modelCatalog.current??modelCatalog.default??{},modelValue=current.provider+'\u001f'+current.model,presetFallback=presets.find(item=>item.isDefault)?.id||presets[0]?.id||'',presetValue=presets.some(item=>item.id===currentPreset)?currentPreset:presetFallback;host.innerHTML='<div class="settings-head"><strong>'+L.sessionSettings+'</strong><button class="settings-close" data-settings-close aria-label="Close">×</button></div><label class="setting-field"><span>'+L.model+'</span><select id="modelSelect"'+(!modelCatalog.groups?.length?' disabled':'')+'>'+(modelCatalog.groups||[]).map(group=>'<optgroup label="'+esc(group.name)+'">'+(group.models||[]).map(model=>'<option value="'+esc(group.id+'\u001f'+model.id)+'">'+esc(model.name)+'</option>').join('')+'</optgroup>').join('')+'</select></label><label id="effortField" class="setting-field" hidden><span>'+L.reasoningEffort+'</span><select id="effortSelect"></select></label><label class="setting-field"><span>'+L.preset+'</span><select id="presetSelect">'+presets.map(item=>'<option value="'+esc(item.id)+'">'+esc(presetLabel(item))+'</option>').join('')+'</select></label><div id="presetNote" class="setting-note"></div><div id="settingsError" class="settings-error">'+(!modelCatalog.groups?.length?L.noModels:'')+'</div>';if((modelCatalog.groups||[]).some(group=>(group.models||[]).some(model=>group.id+'\u001f'+model.id===modelValue)))$('modelSelect').value=modelValue;if($('presetSelect'))$('presetSelect').value=presetValue;syncEffortOptions();syncSettingsControls()}
async function refreshActiveSessionSummary(){if(!activeSession)return;const value=await rpc('session.list',{}),summary=(value.items||[]).find(item=>item.sessionId===activeSession);if(summary){sessionBlank=summary.blank;currentPreset=summary.agentPreset||currentPreset;sessionSummaries=value.items||[]}syncSettingsControls()}
async function openSessionSettings(){if(!$('sessionSettings').hidden){closeSessionSettings();return}const sessionId=activeSession;if(!sessionId)return;$('sessionSettings').hidden=false;$('settings').setAttribute('aria-expanded','true');modelCatalog=null;renderSessionSettings();try{const [models,presetValue,sessions]=await Promise.all([rpc('session.models',{sessionId}),rpc('agentPreset.list',{}),rpc('session.list',{})]);if(activeSession!==sessionId)return;modelCatalog=models;presets=(presetValue.presets||[]).filter(preset=>!preset.broken);sessionSummaries=sessions.items||[];const summary=sessionSummaries.find(item=>item.sessionId===sessionId);if(summary){sessionBlank=summary.blank;currentPreset=summary.agentPreset||currentPreset}renderSessionSettings()}catch(e){if(activeSession!==sessionId)return;modelCatalog={current:{provider:'',model:''},routable:false,groups:[],failures:[]};renderSessionSettings();showError('settingsError',e)}}
async function openSessionSettings(){if(!$('sessionSettings').hidden){closeSessionSettings();return}const sessionId=activeSession;if(!sessionId)return;$('sessionSettings').hidden=false;$('settings').setAttribute('aria-expanded','true');modelCatalog=null;renderSessionSettings();try{const [models,presetValue,sessions]=await Promise.all([rpc('session.models',{sessionId}),rpc('agentPreset.list',{}),rpc('session.list',{})]);if(activeSession!==sessionId)return;modelCatalog=models;presets=(presetValue.presets||[]).filter(preset=>!preset.broken);sessionSummaries=sessions.items||[];const summary=sessionSummaries.find(item=>item.sessionId===sessionId);if(summary){sessionBlank=summary.blank;currentPreset=summary.agentPreset||currentPreset}renderSessionSettings()}catch(e){if(activeSession!==sessionId)return;modelCatalog={default:{provider:'',model:''},routableProviders:[],groups:[],failures:[]};renderSessionSettings();showError('settingsError',e)}}
async function selectPreset(){const select=$('presetSelect');if(!select||settingsBusy||presetIsLocked()||!activeSession)return;const previous=currentPreset,next=select.value;if(!next||next===previous)return;settingsBusy=true;syncSettingsControls();try{const value=await rpc('agentPreset.select',{sessionId:activeSession,agentPreset:next});currentPreset=value.agentPreset;showToast(L.settingsUpdated)}catch(e){select.value=previous||select.value;showError('settingsError',e);await refreshActiveSessionSummary().catch(()=>{})}finally{settingsBusy=false;syncSettingsControls()}}
async function selectModel(){const select=$('modelSelect');if(!select||settingsBusy||!activeSession)return;const entry=selectedModelEntry(),provider=entry.group?.id,model=entry.model?.id;if(!provider||!model)return;settingsBusy=true;syncSettingsControls();const reasoningEffort=$('effortSelect')?.value||entry.model?.reasoning?.defaultEffort;try{const value=await rpc('session.selectModel',{sessionId:activeSession,provider,model,...(reasoningEffort?{reasoningEffort}:{})});modelCatalog.current=value.selected;renderSessionSettings();showToast(L.settingsUpdated)}catch(e){showError('settingsError',e);syncSettingsControls()}finally{settingsBusy=false;syncSettingsControls()}}
async function selectModel(){const select=$('modelSelect');if(!select||settingsBusy||!activeSession)return;const entry=selectedModelEntry(),provider=entry.group?.id,model=entry.model?.id;if(!provider||!model)return;settingsBusy=true;syncSettingsControls();const reasoningEffort=$('effortSelect')?.value||entry.model?.reasoning?.defaultEffort;try{const value=await rpc('session.selectModel',{sessionId:activeSession,provider,model,...(reasoningEffort?{reasoningEffort}:{})});modelCatalog.current=modelCatalog.default=value.selected;renderSessionSettings();showToast(L.settingsUpdated)}catch(e){showError('settingsError',e);syncSettingsControls()}finally{settingsBusy=false;syncSettingsControls()}}
function durableUserTextCounts(messages){const counts=new Map();for(const message of messages){if(message.role!=='user')continue;const text=message.blocks?.filter(block=>block?.kind==='text').map(block=>block.text).join('\\n')||'';counts.set(text,(counts.get(text)||0)+1)}return counts}
function messagesWithOptimistic(durable){const counts=durableUserTextCounts(durable);optimisticPrompts=optimisticPrompts.filter(item=>(counts.get(item.text)||0)<item.targetCount);latestUserTextCounts=counts;lastDurableMessages=durable;return[...durable,...optimisticPrompts.map(item=>({role:'user',blocks:[{kind:'text',text:item.text}],optimistic:true}))]}
function nextOptimisticTarget(text){let target=latestUserTextCounts.get(text)||0;for(const item of optimisticPrompts)if(item.text===text)target=Math.max(target,item.targetCount);return target+1}
Expand Down