diff --git a/web-ui/html/workshop-app.html b/web-ui/html/workshop-app.html index 0418028..b14cb76 100644 --- a/web-ui/html/workshop-app.html +++ b/web-ui/html/workshop-app.html @@ -72,6 +72,16 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis .rb-send:hover:not(:disabled){background:#9a5c03;} .rb-send:disabled{opacity:.5;cursor:not-allowed;} +.rb-model-pill{position:relative;flex-shrink:0;} +.rb-model-pill-btn{background:none;border:1px solid var(--line);border-radius:6px;padding:2px 8px;font-size:10px;font-weight:700;color:var(--muted);cursor:pointer;font-family:inherit;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:.15s;} +.rb-model-pill-btn:hover{border-color:#f59e0b;color:#f59e0b;} +.rb-model-dropdown{display:none;position:absolute;top:100%;right:0;margin-top:4px;background:var(--panel-2);border:1px solid var(--line);border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.35);min-width:200px;max-height:280px;overflow-y:auto;z-index:50;padding:4px;} +.rb-model-dropdown.open{display:block;} +.rb-model-opt{padding:6px 10px;font-size:12px;border-radius:5px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--text);} +.rb-model-opt:hover{background:var(--panel);} +.rb-model-opt.current{color:#f59e0b;font-weight:700;} +.rb-model-dropdown-msg{padding:6px 10px;font-size:11px;color:var(--muted);} + @media (max-width:900px){ .rb-layout{flex-direction:column;} .rb-resize{display:none;} @@ -224,6 +234,10 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
πŸ› οΈ μž‘μ—…μ‹€ μ±„νŒ… +
+ +
+
@@ -847,12 +861,68 @@ function autoResize(el){el.style.height='auto';el.style.height=Math.min(el.scrol document.addEventListener('mouseup',()=>{if(!dragging)return;dragging=false;handle.classList.remove('dragging');document.body.style.userSelect='';document.body.style.cursor='';try{localStorage.setItem(CHAT_W,panel.offsetWidth);}catch{};}); })(); +// ── λͺ¨λΈ pill ───────────────────────────────────────────────────────────────── +// κ²½λŸ‰ 버전: 메인 μ±„νŒ…μ˜ μ„€μ • λͺ¨λ‹¬(settings-provider.js)을 ν†΅μ§Έλ‘œ λ“€μ—¬μ˜€μ§€ μ•Šκ³ , 같은 λ°±μ—”λ“œ +// μ—”λ“œν¬μΈνŠΈ(/api/settings/model, /api/settings/provider, /api/models/test)만 μž¬μ‚¬μš©ν•΄μ„œ +// μ „μ—­ models.primaryλ₯Ό λ°”κΎΌλ‹€. ollama provider(둜컬+ν΄λΌμš°λ“œ λͺ¨λΈ νƒœκ·Έ) μ•ˆμ—μ„œλ§Œ μ „ν™˜λ¨ β€” +// provider 자체(openai/anthropic/google λ“±)λ₯Ό λ°”κΎΈλŠ” 건 메인 μ„€μ •μ—μ„œ ν•΄μ•Ό ν•œλ‹€. +let _modelDropdownLoaded=false; +async function loadModelPill(){ + try{ + const r=await fetch('/api/settings/model',{headers:authH()}); + if(!r.ok)return; + const d=await r.json(); + const btn=document.getElementById('model-pill-btn'); + if(btn && d.primary) btn.textContent=d.primary; + }catch{} +} +async function toggleModelDropdown(ev){ + if(ev) ev.stopPropagation(); + const dd=document.getElementById('model-dropdown'); + if(dd.classList.contains('open')){dd.classList.remove('open');return;} + dd.classList.add('open'); + if(_modelDropdownLoaded)return; + dd.innerHTML='
λͺ¨λΈ λΆˆλŸ¬μ˜€λŠ” 쀑…
'; + try{ + const pr=await fetch('/api/settings/provider',{headers:authH()}); + const pd=await pr.json(); + const tr=await fetch('/api/models/test',{method:'POST',headers:authH({'Content-Type':'application/json'}),body:JSON.stringify({llm:pd.llm})}); + const td=await tr.json(); + const models=(td.models||[]).map(m=>typeof m==='string'?m:(m.name||String(m))); + if(!models.length){dd.innerHTML='
λͺ¨λΈ λͺ©λ‘μ„ κ°€μ Έμ˜€μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.
';return;} + const current=(document.getElementById('model-pill-btn').textContent||'').trim(); + dd.innerHTML=models.map(m=>`
${escHtml(m)}
`).join(''); + dd.querySelectorAll('.rb-model-opt').forEach(el=>el.addEventListener('click',()=>selectModel(el.dataset.model))); + _modelDropdownLoaded=true; + }catch(e){dd.innerHTML='
였λ₯˜: '+escHtml(e.message)+'
';} +} +async function selectModel(name){ + document.getElementById('model-dropdown').classList.remove('open'); + const btn=document.getElementById('model-pill-btn'); + const prev=btn.textContent; + if(name===prev)return; + btn.textContent=name; + try{ + const r=await fetch('/api/settings/model',{method:'POST',headers:authH({'Content-Type':'application/json'}),body:JSON.stringify({primary:name})}); + if(!r.ok)throw new Error('μ €μž₯ μ‹€νŒ¨'); + _modelDropdownLoaded=false; + }catch(e){ + btn.textContent=prev; + addMsg('system','λͺ¨λΈ λ³€κ²½ μ‹€νŒ¨: '+e.message); + } +} +document.addEventListener('click',(e)=>{ + const pill=document.querySelector('.rb-model-pill'); + if(pill && !pill.contains(e.target)) document.getElementById('model-dropdown')?.classList.remove('open'); +}); + // ── Init ────────────────────────────────────────────────────────────────────── (async function init(){ if(!(await checkAuth()))return; await loadWorkshopData(); await initSessionId(); await loadHistory(); + loadModelPill(); addMsg('system','πŸ› οΈ μž‘μ—…μ‹€ μ±„νŒ…μž…λ‹ˆλ‹€. ν˜„μž¬ ν”„λ‘œμ νŠΈ: "'+(currentProject()?.name||'')+'". λΆ€ν’ˆ ꡬ맀/μƒνƒœ λ³€κ²½, μž‘μ—… μ™„λ£Œ 체크, μ˜ˆμ‚° 확인, μƒˆ ν”„λ‘œμ νŠΈ 생성 등을 λ¬Όμ–΄λ³΄μ„Έμš”.'); document.getElementById('chat-input').focus(); })();