diff --git a/backend/app.py b/backend/app.py index 10842df..1a327e5 100644 --- a/backend/app.py +++ b/backend/app.py @@ -420,7 +420,15 @@ class ChangePwReq(BaseModel): class UserUpdateReq(BaseModel): email: Optional[str] = None; role: Optional[str] = None; is_active: Optional[bool] = None class ChatMsg(BaseModel): - message: str; session_id: Optional[str] = None; genai_config_id: Optional[str] = None + message: str; session_id: Optional[str] = None + # Option A: saved preset + genai_config_id: Optional[str] = None + # Option B: direct model selection (inline) + model_id: Optional[str] = None; oci_config_id: Optional[str] = None + genai_region: Optional[str] = None; compartment_id: Optional[str] = None + temperature: Optional[float] = None; max_tokens: Optional[int] = None + top_p: Optional[float] = None; top_k: Optional[int] = None + frequency_penalty: Optional[float] = None; presence_penalty: Optional[float] = None class RunReportReq(BaseModel): config_id: str; mcp_server_id: Optional[str] = None; regions: Optional[List[str]] = None class GenAIConfigReq(BaseModel): @@ -1570,6 +1578,32 @@ async def chat(msg: ChatMsg, u=Depends(current_user)): if msg.genai_config_id: with db() as c: genai_cfg = c.execute("SELECT * FROM genai_configs WHERE id=?", (msg.genai_config_id,)).fetchone() + elif msg.model_id and msg.oci_config_id: + # Direct model mode: build synthetic config dict + with db() as c: + oci_row = c.execute("SELECT * FROM oci_configs WHERE id=?", (msg.oci_config_id,)).fetchone() + if not oci_row: + raise HTTPException(400, "OCI config not found") + region = msg.genai_region or oci_row["region"] + compartment = msg.compartment_id or oci_row.get("compartment_id") or "" + if not compartment: + raise HTTPException(400, "compartment_id required") + genai_cfg = { + "oci_config_id": msg.oci_config_id, + "model_id": msg.model_id, + "model_ocid": None, + "compartment_id": compartment, + "genai_region": region, + "endpoint": f"https://inference.generativeai.{region}.oci.oraclecloud.com", + "serving_type": "ON_DEMAND", + "dedicated_endpoint_id": None, + "temperature": msg.temperature if msg.temperature is not None else 1.0, + "max_tokens": msg.max_tokens if msg.max_tokens is not None else 6000, + "top_p": msg.top_p if msg.top_p is not None else 0.95, + "top_k": msg.top_k if msg.top_k is not None else 1, + "frequency_penalty": msg.frequency_penalty if msg.frequency_penalty is not None else 0.0, + "presence_penalty": msg.presence_penalty if msg.presence_penalty is not None else 0.0, + } if genai_cfg: try: diff --git a/frontend/index.html b/frontend/index.html index 489cefa..1bf6da3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -115,7 +115,13 @@ tbody tr:last-child td{border-bottom:none} .ch-c{display:flex;flex-direction:column;height:calc(100vh - 56px - 3rem);overflow:hidden} .ch-t{display:flex;gap:.5rem;align-items:center;padding:.6rem .85rem;background:var(--bg1);border:1px solid var(--bd); border-radius:var(--rl) var(--rl) 0 0;font-size:.74rem;flex-wrap:wrap;box-shadow:var(--sh1)} -.ch-t select{max-width:260px;font-size:.74rem;padding:.35rem .5rem} +.ch-t select{max-width:260px;font-size:.74rem;padding:.35rem .5rem}.ch-t select.cmod-sel{max-width:320px} +.ch-params{padding:.55rem .85rem;background:var(--bg1);border-left:1px solid var(--bd);border-right:1px solid var(--bd);border-bottom:1px solid var(--bg3);font-size:.72rem} +.ch-params .g3{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem;margin-bottom:.3rem} +.ch-params label{font-weight:600;color:var(--t3);font-size:.66rem;margin-bottom:2px;display:block} +.ch-params input{width:100%;padding:.25rem .4rem;font-size:.72rem;border:1px solid var(--bd);border-radius:6px;background:var(--bg2)} +.gear-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:8px;cursor:pointer;border:1px solid var(--bd);background:var(--bg1);font-size:.78rem;transition:all .2s;flex-shrink:0} +.gear-btn:hover{border-color:var(--ac);color:var(--ac)} .ch-t label{font-weight:600;color:var(--t3);font-size:.72rem} .ch-m{flex:1;overflow-y:auto;padding:1rem;background:var(--bg2);border-left:1px solid var(--bd);border-right:1px solid var(--bd)} .cm{margin-bottom:.85rem;display:flex;gap:.6rem;animation:msgIn .3s var(--trans)}.cm-user{justify-content:flex-end} @@ -194,7 +200,9 @@ const V='1.1'; const LOGO_W=``; const LOGO_R=``; -const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],genaiCfg:[],adbCfg:[],mcpSvr:[],users:[],auditLogs:[],models:{},regions:[],embModels:{},selGenai:'',expData:null,editing:null}; +const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],genaiCfg:[],adbCfg:[],mcpSvr:[],users:[],auditLogs:[],models:{},regions:[],embModels:{},selGenai:'',expData:null,editing:null, + chatModel:'',chatOci:'',chatRegion:'',chatCompartment:'', + chatParams:{temperature:1,max_tokens:6000,top_p:0.95,top_k:1,frequency_penalty:0,presence_penalty:0},chatParamsOpen:false}; const API='/api'; async function $api(p,o={}){const h={...(o.headers||{})};if(S.token)h['Authorization']='Bearer '+S.token; @@ -255,20 +263,52 @@ function rPg(){switch(S.tab){case'chat':return rChat();case'explorer':return rEx /* ── Chat ── */ function rChat(){ - const ms=S.msgs.length===0?'
🤖

Inicie uma conversa com o agente.

Selecione um modelo GenAI ou use o agente local.

' + const ms=S.msgs.length===0?'
🤖

Inicie uma conversa com o agente.

Selecione um modelo ou use o agente local.

' :S.msgs.map(m=>`
${fm(m.c)}
`).join(''); - const gcOpts=S.genaiCfg.map(g=>{const mi=S.models[g.model_id];return``}).join(''); + // Build model select with optgroups + const presetOpts=S.genaiCfg.map(g=>{const mi=S.models[g.model_id];return``}).join(''); + const skip=new Set(['openai.gpt-image-1','openai.gpt-image-1.5','openai.gpt-audio','meta.llama-guard-4-12b','protectai.deberta-v3-base-prompt-injection-v2']); + const provs={};for(const[mid,info]of Object.entries(S.models)){if(skip.has(mid))continue;const p=info.provider||'other';if(!provs[p])provs[p]=[];provs[p].push({id:mid,name:info.name})} + 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=>``).join('');return`${opts}`}).join(''); + const isDirect=S.chatModel&&!S.chatModel.startsWith('cfg:'); + // OCI selector for direct mode + const ociSel=isDirect?``:''; + const gearBtn=isDirect?`
${S.chatParamsOpen?'✕':'⚙️'}
`:''; + const ragBadge=S.adbCfg.some(c=>c.genai_config_id&&c.is_active)?'RAG':''; + // Params panel + const pp=S.chatParams; + const paramsPanel=isDirect&&S.chatParamsOpen?`
+
+
+
+
+
+
+
+
`:''; return`
-
-${S.adbCfg.some(c=>c.genai_config_id&&c.is_active)?'RAG Ativo':''} -
-
${ms}
+
+${ociSel}${gearBtn}${ragBadge}
+${paramsPanel}
${ms}
`} +function chatModelChanged(){S.chatModel=document.getElementById('cmod').value; + if(S.chatModel.startsWith('cfg:')){S.chatParamsOpen=false} + 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()} +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 fm(t){return t.replace(/\*\*(.*?)\*\*/g,'$1').replace(/`(.*?)`/g,'$1').replace(/\n/g,'
')} 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(); - try{const body={message:m,session_id:S.sid};if(S.selGenai)body.genai_config_id=S.selGenai; + try{const body={message:m,session_id:S.sid}; + if(S.chatModel.startsWith('cfg:')){body.genai_config_id=S.chatModel.slice(4)} + else if(S.chatModel){ + 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.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} const d=await $api('/chat',{method:'POST',body});S.sid=d.session_id;S.msgs.push({r:'assistant',c:d.response});R();scCh()} catch(e){S.msgs.push({r:'assistant',c:'❌ Erro: '+e.message});R()}} function scCh(){setTimeout(()=>{const e=document.getElementById('chm');if(e)e.scrollTop=e.scrollHeight},50)}