refactor: move GenAI parameters to Chat tab, simplify GenAI Config

Remove temperature, max_tokens, top_p, top_k, frequency/presence
penalty fields from the GenAI Config tab. That tab is now focused
on adding custom models not in the catalog.

Parameters are now managed exclusively in the Chat tab via the
collapsible gear panel, available for both direct models and saved
configs. Inline params override stored defaults when using presets.
This commit is contained in:
nogueiraguh
2026-03-03 20:22:47 -03:00
parent 13b98209d5
commit 749875a3cd
2 changed files with 22 additions and 21 deletions

View File

@@ -1577,7 +1577,16 @@ async def chat(msg: ChatMsg, u=Depends(current_user)):
genai_cfg = None genai_cfg = None
if msg.genai_config_id: if msg.genai_config_id:
with db() as c: with db() as c:
genai_cfg = c.execute("SELECT * FROM genai_configs WHERE id=?", (msg.genai_config_id,)).fetchone() row = c.execute("SELECT * FROM genai_configs WHERE id=?", (msg.genai_config_id,)).fetchone()
if row:
genai_cfg = dict(row)
# Override params from inline chat settings if provided
if msg.temperature is not None: genai_cfg["temperature"] = msg.temperature
if msg.max_tokens is not None: genai_cfg["max_tokens"] = msg.max_tokens
if msg.top_p is not None: genai_cfg["top_p"] = msg.top_p
if msg.top_k is not None: genai_cfg["top_k"] = msg.top_k
if msg.frequency_penalty is not None: genai_cfg["frequency_penalty"] = msg.frequency_penalty
if msg.presence_penalty is not None: genai_cfg["presence_penalty"] = msg.presence_penalty
elif msg.model_id and msg.oci_config_id: elif msg.model_id and msg.oci_config_id:
# Direct model mode: build synthetic config dict # Direct model mode: build synthetic config dict
with db() as c: with db() as c:

View File

@@ -272,13 +272,14 @@ function rChat(){
const provOrder=['openai','google','meta','cohere','xai']; const provOrder=['openai','google','meta','cohere','xai'];
const catalogOpts=provOrder.filter(p=>provs[p]).map(p=>{const label=p.charAt(0).toUpperCase()+p.slice(1);const opts=provs[p].map(m=>`<option value="${m.id}" ${S.chatModel===m.id?'selected':''}>${m.name}</option>`).join('');return`<optgroup label="${label}">${opts}</optgroup>`}).join(''); const catalogOpts=provOrder.filter(p=>provs[p]).map(p=>{const label=p.charAt(0).toUpperCase()+p.slice(1);const opts=provs[p].map(m=>`<option value="${m.id}" ${S.chatModel===m.id?'selected':''}>${m.name}</option>`).join('');return`<optgroup label="${label}">${opts}</optgroup>`}).join('');
const isDirect=S.chatModel&&!S.chatModel.startsWith('cfg:'); const isDirect=S.chatModel&&!S.chatModel.startsWith('cfg:');
const hasModel=!!S.chatModel;
// OCI selector for direct mode // OCI selector for direct mode
const ociSel=isDirect?`<select id="coci" onchange="chatOciChanged()" style="max-width:200px"><option value="">Credencial OCI...</option>${S.ociCfg.map(c=>`<option value="${c.id}" ${S.chatOci===c.id?'selected':''}>${c.tenancy_name} (${c.region})</option>`).join('')}</select>`:''; const ociSel=isDirect?`<select id="coci" onchange="chatOciChanged()" style="max-width:200px"><option value="">Credencial OCI...</option>${S.ociCfg.map(c=>`<option value="${c.id}" ${S.chatOci===c.id?'selected':''}>${c.tenancy_name} (${c.region})</option>`).join('')}</select>`:'';
const gearBtn=isDirect?`<div class="gear-btn" onclick="toggleChatParams()" title="Parâmetros">${S.chatParamsOpen?'✕':'⚙️'}</div>`:''; const gearBtn=hasModel?`<div class="gear-btn" onclick="toggleChatParams()" title="Parâmetros">${S.chatParamsOpen?'✕':'⚙️'}</div>`:'';
const ragBadge=S.adbCfg.some(c=>c.genai_config_id&&c.is_active)?'<span class="tag" style="background:var(--gnl);color:var(--gn);font-size:.62rem">RAG</span>':''; const ragBadge=S.adbCfg.some(c=>c.genai_config_id&&c.is_active)?'<span class="tag" style="background:var(--gnl);color:var(--gn);font-size:.62rem">RAG</span>':'';
// Params panel // Params panel - available for any model (direct or preset)
const pp=S.chatParams; const pp=S.chatParams;
const paramsPanel=isDirect&&S.chatParamsOpen?`<div class="ch-params"><div class="g3"> const paramsPanel=hasModel&&S.chatParamsOpen?`<div class="ch-params"><div class="g3">
<div><label>Temperature</label><input type="number" value="${pp.temperature}" step="0.1" min="0" max="2" onchange="S.chatParams.temperature=+this.value"></div> <div><label>Temperature</label><input type="number" value="${pp.temperature}" step="0.1" min="0" max="2" onchange="S.chatParams.temperature=+this.value"></div>
<div><label>Max Tokens</label><input type="number" value="${pp.max_tokens}" min="1" max="128000" onchange="S.chatParams.max_tokens=+this.value"></div> <div><label>Max Tokens</label><input type="number" value="${pp.max_tokens}" min="1" max="128000" onchange="S.chatParams.max_tokens=+this.value"></div>
<div><label>Top P</label><input type="number" value="${pp.top_p}" step="0.05" min="0" max="1" onchange="S.chatParams.top_p=+this.value"></div> <div><label>Top P</label><input type="number" value="${pp.top_p}" step="0.05" min="0" max="1" onchange="S.chatParams.top_p=+this.value"></div>
@@ -294,8 +295,7 @@ ${paramsPanel}<div class="ch-m" id="chm">${ms}</div>
<div class="ch-i"><input type="text" id="chi" placeholder="Digite sua mensagem..." onkeydown="if(event.key==='Enter')sChat()"> <div class="ch-i"><input type="text" id="chi" placeholder="Digite sua mensagem..." onkeydown="if(event.key==='Enter')sChat()">
<button class="btn bp" onclick="sChat()">Enviar →</button></div></div>`} <button class="btn bp" onclick="sChat()">Enviar →</button></div></div>`}
function chatModelChanged(){S.chatModel=document.getElementById('cmod').value; function chatModelChanged(){S.chatModel=document.getElementById('cmod').value;
if(S.chatModel.startsWith('cfg:')){S.chatParamsOpen=false} if(S.chatModel&&!S.chatModel.startsWith('cfg:')&&!S.chatOci&&S.ociCfg.length){S.chatOci=S.ociCfg[0].id;const c=S.ociCfg[0];S.chatRegion=c.region;S.chatCompartment=c.compartment_id||''}
else if(S.chatModel&&!S.chatOci&&S.ociCfg.length){S.chatOci=S.ociCfg[0].id;const c=S.ociCfg[0];S.chatRegion=c.region;S.chatCompartment=c.compartment_id||''}
R()} R()}
function chatOciChanged(){S.chatOci=document.getElementById('coci').value;const c=S.ociCfg.find(x=>x.id===S.chatOci);if(c){S.chatRegion=c.region;S.chatCompartment=c.compartment_id||''}} function chatOciChanged(){S.chatOci=document.getElementById('coci').value;const c=S.ociCfg.find(x=>x.id===S.chatOci);if(c){S.chatRegion=c.region;S.chatCompartment=c.compartment_id||''}}
function toggleChatParams(){S.chatParamsOpen=!S.chatParamsOpen;R()} function toggleChatParams(){S.chatParamsOpen=!S.chatParamsOpen;R()}
@@ -303,7 +303,10 @@ function fm(t){return t.replace(/\*\*(.*?)\*\*/g,'<strong>$1</strong>').replace(
async function sChat(){const el=document.getElementById('chi');const m=el.value.trim();if(!m)return;el.value=''; async function sChat(){const el=document.getElementById('chi');const m=el.value.trim();if(!m)return;el.value='';
S.msgs.push({r:'user',c:m});R();scCh(); S.msgs.push({r:'user',c:m});R();scCh();
try{const body={message:m,session_id:S.sid}; try{const body={message:m,session_id:S.sid};
if(S.chatModel.startsWith('cfg:')){body.genai_config_id=S.chatModel.slice(4)} if(S.chatModel.startsWith('cfg:')){
body.genai_config_id=S.chatModel.slice(4);
body.temperature=S.chatParams.temperature;body.max_tokens=S.chatParams.max_tokens;body.top_p=S.chatParams.top_p;
body.top_k=S.chatParams.top_k;body.frequency_penalty=S.chatParams.frequency_penalty;body.presence_penalty=S.chatParams.presence_penalty}
else if(S.chatModel){ else if(S.chatModel){
if(!S.chatOci){S.msgs.push({r:'assistant',c:'⚠️ Selecione uma credencial OCI para usar o modelo direto.'});R();return} if(!S.chatOci){S.msgs.push({r:'assistant',c:'⚠️ Selecione uma credencial OCI para usar o modelo direto.'});R();return}
body.model_id=S.chatModel;body.oci_config_id=S.chatOci;body.genai_region=S.chatRegion;body.compartment_id=S.chatCompartment; body.model_id=S.chatModel;body.oci_config_id=S.chatOci;body.genai_region=S.chatRegion;body.compartment_id=S.chatCompartment;
@@ -418,29 +421,21 @@ function rGenAI(){const eg=S.editing?.type==='genai'?S.genaiCfg.find(g=>g.id===S
const mOpts='<option value="_custom"'+(eg&&eg.model_ocid?' selected':'')+'>🔗 Personalizado (usar OCID)</option>'+Object.entries(S.models).map(([k,v])=>`<option value="${k}"${eg&&eg.model_id===k&&!eg.model_ocid?' selected':''}>${v.name} (${v.provider})</option>`).join(''); const mOpts='<option value="_custom"'+(eg&&eg.model_ocid?' selected':'')+'>🔗 Personalizado (usar OCID)</option>'+Object.entries(S.models).map(([k,v])=>`<option value="${k}"${eg&&eg.model_id===k&&!eg.model_ocid?' selected':''}>${v.name} (${v.provider})</option>`).join('');
const rOpts=S.regions.map(r=>`<option value="${r}" ${eg?(r===eg.genai_region?'selected':''):(r==='us-ashburn-1'?'selected':'')}>${r}</option>`).join(''); const rOpts=S.regions.map(r=>`<option value="${r}" ${eg?(r===eg.genai_region?'selected':''):(r==='us-ashburn-1'?'selected':'')}>${r}</option>`).join('');
return`<div class="cd"><div class="ct">🧠 Modelos Configurados</div> return`<div class="cd"><div class="ct">🧠 Modelos Configurados</div>
<table><thead><tr><th>Config</th><th>Modelo</th><th>Região</th><th>Temp</th><th>Tokens</th><th>Ações</th></tr></thead><tbody> <table><thead><tr><th>Config</th><th>Modelo</th><th>Região</th><th>Ações</th></tr></thead><tbody>
${S.genaiCfg.map(g=>{const mi=S.models[g.model_id]||{};return`<tr${eg&&eg.id===g.id?' style="background:var(--bg3)"':''}><td><strong>${g.name||'—'}</strong>${g.is_default?' ':''}</td> ${S.genaiCfg.map(g=>{const mi=S.models[g.model_id]||{};return`<tr${eg&&eg.id===g.id?' style="background:var(--bg3)"':''}><td><strong>${g.name||'—'}</strong>${g.is_default?' ':''}</td>
<td>${mi.name||(g.model_ocid?'OCID Personalizado':g.model_id)}<br><span class="tag">${mi.provider||(g.model_ocid?'custom':'?')}</span></td> <td>${mi.name||(g.model_ocid?'OCID Personalizado':g.model_id)}<br><span class="tag">${mi.provider||(g.model_ocid?'custom':'?')}</span></td>
<td style="font-family:var(--fm);font-size:.7rem">${g.genai_region}</td> <td style="font-family:var(--fm);font-size:.7rem">${g.genai_region}</td>
<td>${g.temperature}</td><td>${g.max_tokens}</td>
<td><button class="btn bs bsm" onclick="editCfg('genai','${g.id}')">Editar</button> <button class="btn bs bsm" onclick="tGenai('${g.id}')">Testar</button> <button class="btn bd bsm" onclick="dGenai('${g.id}')">Excluir</button></td></tr>`}).join('')}</tbody></table> <td><button class="btn bs bsm" onclick="editCfg('genai','${g.id}')">Editar</button> <button class="btn bs bsm" onclick="tGenai('${g.id}')">Testar</button> <button class="btn bd bsm" onclick="dGenai('${g.id}')">Excluir</button></td></tr>`}).join('')}</tbody></table>
${!S.genaiCfg.length?'<div class="emp" style="padding:.85rem"><p>Nenhum modelo configurado.</p></div>':''}</div> ${!S.genaiCfg.length?'<div class="emp" style="padding:.85rem"><p>Nenhum modelo configurado.</p></div>':''}</div>
<div class="cd"><div class="ct">${eg?'✏️ Editar Modelo GenAI':' Novo Modelo GenAI'}</div> <div class="cd"><div class="ct">${eg?'✏️ Editar Modelo GenAI':' Novo Modelo GenAI'}</div>
<div class="cdesc">${eg?'Editando: <strong>'+eg.name+'</strong>':'Configura conexão via OCI SDK (<code style="font-size:.72rem">GenerativeAiInferenceClient</code>). O endpoint é gerado automaticamente pela região selecionada.'}</div><div id="gm"></div> <div class="cdesc">${eg?'Editando: <strong>'+eg.name+'</strong>':'Adicione modelos personalizados que não estão no catálogo do Chat Agent, ou crie presets com credenciais específicas.'}</div><div id="gm"></div>
<div class="g3"><div class="ig"><label>Nome da Config</label><input type="text" id="gname" placeholder="Llama Produção" value="${eg?eg.name:''}"></div> <div class="g3"><div class="ig"><label>Nome da Config</label><input type="text" id="gname" placeholder="Meu Modelo Custom" value="${eg?eg.name:''}"></div>
<div class="ig"><label>Credencial OCI</label><select id="goci" onchange="fillGenaiFromOci()"><option value="">Selecione...</option> <div class="ig"><label>Credencial OCI</label><select id="goci" onchange="fillGenaiFromOci()"><option value="">Selecione...</option>
${S.ociCfg.map(c=>`<option value="${c.id}"${eg&&eg.oci_config_id===c.id?' selected':''}>${c.tenancy_name} (${c.region})</option>`).join('')}</select></div> ${S.ociCfg.map(c=>`<option value="${c.id}"${eg&&eg.oci_config_id===c.id?' selected':''}>${c.tenancy_name} (${c.region})</option>`).join('')}</select></div>
<div class="ig"><label>Modelo</label><select id="gmod">${mOpts}</select></div></div> <div class="ig"><label>Modelo</label><select id="gmod">${mOpts}</select></div></div>
<div class="g3"><div class="ig"><label>Região GenAI</label><select id="greg">${rOpts}</select></div> <div class="g3"><div class="ig"><label>Região GenAI</label><select id="greg">${rOpts}</select></div>
<div class="ig"><label>Compartment OCID</label><input type="text" id="gcmp" placeholder="ocid1.compartment.oc1.." value="${eg?eg.compartment_id:''}"></div> <div class="ig"><label>Compartment OCID</label><input type="text" id="gcmp" placeholder="ocid1.compartment.oc1.." value="${eg?eg.compartment_id:''}"></div>
<div class="ig"><label>Model OCID</label><div class="ht">Cole o OCID do modelo do portal OCI. Se preenchido, será usado no lugar do modelo selecionado acima.</div><input type="text" id="gocid" placeholder="ocid1.generativeaimodel.oc1.iad.amaaaaaa..." value="${eg&&eg.model_ocid?eg.model_ocid:''}" style="${eg&&eg.model_ocid?'border-color:var(--p)':''}"></div></div> <div class="ig"><label>Model OCID</label><div class="ht">Cole o OCID do modelo do portal OCI. Se preenchido, será usado no lugar do modelo selecionado acima.</div><input type="text" id="gocid" placeholder="ocid1.generativeaimodel.oc1.iad.amaaaaaa..." value="${eg&&eg.model_ocid?eg.model_ocid:''}" style="${eg&&eg.model_ocid?'border-color:var(--p)':''}"></div></div>
<div style="font-size:.76rem;font-weight:700;color:var(--t2);margin:.65rem 0 .4rem;letter-spacing:-.01em">Parâmetros</div>
<div class="g3"><div class="ig"><label>Temperature</label><input type="number" id="gtemp" value="${eg?eg.temperature:1}" step="0.1" min="0" max="2"></div>
<div class="ig"><label>Max Tokens</label><input type="number" id="gmt" value="${eg?eg.max_tokens:6000}" min="1" max="128000"></div>
<div class="ig"><label>Top P</label><input type="number" id="gtp" value="${eg?eg.top_p:0.95}" step="0.05" min="0" max="1"></div></div>
<div class="g3"><div class="ig"><label>Top K</label><input type="number" id="gtk" value="${eg?eg.top_k:1}" min="-1" max="500"></div>
<div class="ig"><label>Frequency Penalty</label><input type="number" id="gfp" value="${eg?eg.frequency_penalty:0}" step="0.1" min="0" max="2"></div>
<div class="ig"><label>Presence Penalty</label><input type="number" id="gpp" value="${eg?eg.presence_penalty:0}" step="0.1" min="0" max="2"></div></div>
<div style="display:flex;gap:8px"><button class="btn bp" id="gbtn" onclick="sGenai()">${eg?'Salvar Alterações':'Salvar Modelo'}</button>${eg?'<button class="btn bs" onclick="cancelEdit()">Cancelar</button>':''}</div></div> <div style="display:flex;gap:8px"><button class="btn bp" id="gbtn" onclick="sGenai()">${eg?'Salvar Alterações':'Salvar Modelo'}</button>${eg?'<button class="btn bs" onclick="cancelEdit()">Cancelar</button>':''}</div></div>
`+rConfigLogs('genai')} `+rConfigLogs('genai')}
async function sGenai(){const btn=document.getElementById('gbtn');btn.disabled=true;btn.textContent='Salvando...';sm('gm','<span class="spinner" style="display:inline-block;width:14px;height:14px;vertical-align:middle"></span> Salvando modelo...','i'); async function sGenai(){const btn=document.getElementById('gbtn');btn.disabled=true;btn.textContent='Salvando...';sm('gm','<span class="spinner" style="display:inline-block;width:14px;height:14px;vertical-align:middle"></span> Salvando modelo...','i');
@@ -453,9 +448,6 @@ async function sGenai(){const btn=document.getElementById('gbtn');btn.disabled=t
model_ocid:ocid, model_ocid:ocid,
compartment_id:document.getElementById('gcmp').value,genai_region:document.getElementById('greg').value, compartment_id:document.getElementById('gcmp').value,genai_region:document.getElementById('greg').value,
endpoint:null,serving_type:'ON_DEMAND',dedicated_endpoint_id:null, endpoint:null,serving_type:'ON_DEMAND',dedicated_endpoint_id:null,
temperature:parseFloat(document.getElementById('gtemp').value),max_tokens:parseInt(document.getElementById('gmt').value),
top_p:parseFloat(document.getElementById('gtp').value),top_k:parseInt(document.getElementById('gtk').value),
frequency_penalty:parseFloat(document.getElementById('gfp').value),presence_penalty:parseFloat(document.getElementById('gpp').value),
is_default:isEdit?(S.genaiCfg.find(g=>g.id===S.editing.id)?.is_default||false):S.genaiCfg.length===0}; is_default:isEdit?(S.genaiCfg.find(g=>g.id===S.editing.id)?.is_default||false):S.genaiCfg.length===0};
try{const url=isEdit?'/genai/configs/'+S.editing.id:'/genai/config';const method=isEdit?'PUT':'POST'; try{const url=isEdit?'/genai/configs/'+S.editing.id:'/genai/config';const method=isEdit?'PUT':'POST';
await $api(url,{method,body});S.editing=null;S.genaiCfg=await $api('/genai/configs');sm('gm','✅ Modelo '+(isEdit?'atualizado':'salvo')+' com sucesso!','s');R()}catch(e){sm('gm',e.message,'e');btn.disabled=false;btn.textContent=isEdit?'Salvar Alterações':'Salvar Modelo'}} await $api(url,{method,body});S.editing=null;S.genaiCfg=await $api('/genai/configs');sm('gm','✅ Modelo '+(isEdit?'atualizado':'salvo')+' com sucesso!','s');R()}catch(e){sm('gm',e.message,'e');btn.disabled=false;btn.textContent=isEdit?'Salvar Alterações':'Salvar Modelo'}}