diff --git a/web/html/index.html b/web/html/index.html index ef466798..a1ba4104 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -1,1737 +1,1268 @@ - + - - General Bots - - - - - +*{margin:0;padding:0;box-sizing:border-box} +body{ +font-family:"Inter",sans-serif; +background:var(--bg); +color:var(--fg); +overflow:hidden; +transition:background 0.3s, color 0.3s; +display:flex; +flex-direction:column; +height:100vh; +position:relative; +} +body::before{ +content:''; +position:fixed; +inset:0; +background:var(--gradient-1); +pointer-events:none; +z-index:0; +} +.float-menu{ +position:fixed; +left:20px; +top:20px; +display:flex; +flex-direction:column; +gap:8px; +z-index:1000; +} +.float-logo{ +width:40px; +height:40px; +background:var(--logo-url) center/contain no-repeat; +filter:var(--logo-filter, none); +border-radius:50%; +cursor:pointer; +transition:all 0.3s; +border:1px solid var(--border); +backdrop-filter:blur(10px); +} +[data-theme="dark"] .float-logo{ +} +.float-logo:hover{ +transform:scale(1.1) rotate(5deg); +} +.menu-button{ +width:40px; +height:40px; +border-radius:50%; +display:flex; +align-items:center; +justify-content:center; +cursor:pointer; +transition:all 0.3s; +background:var(--bg); +border:1px solid var(--border); +font-size:16px; +color:var(--fg); +backdrop-filter:blur(10px); +} +.menu-button:hover{ +transform:scale(1.1) rotate(-5deg); +background:var(--fg); +color:var(--bg); +} +.sidebar{ +position:fixed; +left:-320px; +top:0; +width:320px; +height:100vh; +background:var(--bg); +border-right:1px solid var(--border); +transition:left 0.4s cubic-bezier(0.4,0,0.2,1); +z-index:999; +overflow-y:auto; +padding:20px; +backdrop-filter:blur(20px); +box-shadow:4px 0 20px var(--shadow); +} +.sidebar.open{ +left:0; +} +.sidebar-header{ +display:flex; +align-items:center; +gap:12px; +margin-bottom:30px; +padding-top:10px; +} +.sidebar-logo{ +width:32px; +height:32px; +background:var(--logo-url) center/contain no-repeat; +filter:var(--logo-filter, none); +} +[data-theme="dark"] .sidebar-logo{ +} +.sidebar-title{ +font-size:16px; +font-weight:500; +} +.sidebar-button{ +width:100%; +padding:12px 16px; +border-radius:12px; +cursor:pointer; +transition:all 0.3s; +font-weight:500; +font-size:14px; +margin-bottom:8px; +background:var(--glass); +border:1px solid var(--border); +color:var(--fg); +text-align:left; +} +.sidebar-button:hover{ +background:var(--fg); +color:var(--bg); +transform:translateX(4px) scale(1.02); +} +.history-section{ +margin-top:20px; +} +.history-title{ +font-size:12px; +opacity:0.5; +margin-bottom:12px; +text-transform:uppercase; +letter-spacing:0.5px; +} +.history-item{ +padding:10px 14px; +margin-bottom:6px; +border-radius:10px; +cursor:pointer; +transition:all 0.3s; +font-size:13px; +border:1px solid transparent; +} +.history-item:hover{ +background:var(--fg); +color:var(--bg); +transform:translateX(4px) scale(1.02); +} +#messages{ +flex:1; +overflow-y:auto; +padding:20px 20px 140px; +max-width:680px; +margin:0 auto; +width:100%; +position:relative; +z-index:1; +} +.message-container{ +margin-bottom:24px; +opacity:0; +transform:translateY(10px); +} +.user-message{ +display:flex; +justify-content:flex-end; +margin-bottom:8px; +} +.user-message-content{ +background:var(--fg); +color:var(--bg); +border-radius:18px; +padding:12px 18px; +max-width:80%; +font-size:14px; +line-height:1.5; +box-shadow:0 2px 8px var(--shadow); +position:relative; +overflow:hidden; +} +.user-message-content::before{ +content:''; +position:absolute; +inset:0; +background:var(--gradient-2); +opacity:0.3; +pointer-events:none; +} +.assistant-message{ +display:flex; +gap:8px; +align-items:flex-start; +} +.assistant-avatar{ +width:24px; +height:24px; +border-radius:50%; +background:var(--logo-url) center/contain no-repeat; +flex-shrink:0; +margin-top:2px; +filter:var(--logo-filter, none); +} +[data-theme="dark"] .assistant-avatar{ +} +.assistant-message-content{ +flex:1; +font-size:14px; +line-height:1.7; +background:var(--glass); +border-radius:18px; +padding:12px 18px; +border:1px solid var(--border); +box-shadow:0 2px 8px var(--shadow); +position:relative; +overflow:hidden; +} +.assistant-message-content::before{ +content:''; +position:absolute; +inset:0; +background:var(--gradient-1); +opacity:0.5; +pointer-events:none; +} +.thinking-indicator{ +display:flex; +gap:8px; +align-items:center; +font-size:13px; +opacity:0.4; +} +.typing-dots{ +display:flex; +gap:4px; +} +.typing-dot{ +width:4px; +height:4px; +background:var(--fg); +border-radius:50%; +animation:bounce 1.4s infinite; +} +.typing-dot:nth-child(1){animation-delay:-.32s} +.typing-dot:nth-child(2){animation-delay:-.16s} +@keyframes bounce{ +0%,80%,100%{transform:scale(0);opacity:.3} +40%{transform:scale(1);opacity:1} +} +footer{ +position:fixed; +bottom:0; +left:0; +right:0; +background:var(--bg); +border-top:1px solid var(--border); +padding:12px; +z-index:100; +transition:all 0.3s; +backdrop-filter:blur(20px); +} +.suggestions-container{ +display:flex; +flex-wrap:wrap; +gap:4px; +margin-bottom:8px; +justify-content:center; +max-width:680px; +margin:0 auto 8px; +} +.suggestion-button{ +padding:6px 12px; +border-radius:12px; +cursor:pointer; +font-size:11px; +font-weight:400; +transition:all 0.2s; +background:var(--glass); +border:1px solid var(--border); +color:var(--fg); +} +.suggestion-button:hover{ +background:var(--fg); +color:var(--bg); +transform:scale(1.05); +} +.input-container{ +display:flex; +gap:6px; +max-width:680px; +margin:0 auto; +align-items:center; +} +#messageInput{ +flex:1; +border-radius:20px; +padding:10px 16px; +font-size:14px; +font-family:"Inter",sans-serif; +outline:none; +transition:all 0.3s; +background:var(--glass); +border:1px solid var(--border); +color:var(--fg); +backdrop-filter:blur(10px); +} +#messageInput:focus{ +border-color:var(--accent); +box-shadow:0 0 0 3px rgba(0,102,255,0.1); +} +#messageInput::placeholder{ +opacity:0.3; +} +#sendBtn,#voiceBtn{ +width:36px; +height:36px; +border-radius:18px; +display:flex; +align-items:center; +justify-content:center; +cursor:pointer; +transition:all 0.2s; +border:none; +background:var(--fg); +color:var(--bg); +font-size:16px; +flex-shrink:0; +} +#sendBtn:hover,#voiceBtn:hover{ +transform:scale(1.08) rotate(5deg); +} +#sendBtn:active,#voiceBtn:active{ +transform:scale(0.95); +} +#voiceBtn.recording{ +animation:pulse 1.5s infinite; +} +@keyframes pulse{ +0%,100%{opacity:1;transform:scale(1)} +50%{opacity:0.6;transform:scale(1.1)} +} +.flash-overlay{ +position:fixed; +inset:0; +background:var(--fg); +opacity:0; +pointer-events:none; +z-index:9999; +} +.scroll-to-bottom{ +position:fixed; +bottom:80px; +right:20px; +width:40px; +height:40px; +background:var(--fg); +border:1px solid var(--border); +border-radius:50%; +color:var(--bg); +font-size:18px; +cursor:pointer; +display:none; +align-items:center; +justify-content:center; +transition:all 0.3s; +z-index:90; +} +.scroll-to-bottom.visible{ +display:flex; +} +.scroll-to-bottom:hover{ +transform:scale(1.1) rotate(180deg); +} +.warning-message{ +border-radius:12px; +padding:12px 16px; +margin-bottom:18px; +opacity:0.6; +background:var(--glass); +border:1px solid var(--border); +font-size:13px; +} +.continue-button{ +display:inline-block; +border-radius:10px; +padding:8px 16px; +font-weight:500; +cursor:pointer; +margin-top:10px; +transition:all 0.3s; +font-size:13px; +background:var(--glass); +border:1px solid var(--border); +} +.continue-button:hover{ +background:var(--fg); +color:var(--bg); +transform:translateY(-2px); +} +.context-indicator{ +position:fixed; +bottom:130px; +right:20px; +width:120px; +border-radius:12px; +padding:10px; +font-size:10px; +text-align:center; +z-index:90; +background:var(--bg); +border:1px solid var(--border); +display:none; +backdrop-filter:blur(10px); +} +.context-indicator.visible{ +display:block; +} +.context-progress{ +height:3px; +background:var(--glass); +border-radius:2px; +margin-top:6px; +overflow:hidden; +} +.context-progress-bar{ +height:100%; +background:var(--accent); +border-radius:2px; +transition:width 0.3s; +} +.connection-status{ +position:fixed; +top:20px; +right:20px; +width:8px; +height:8px; +border-radius:50%; +z-index:1000; +transition:all 0.3s; +} +.connection-status.connecting{ +background:var(--fg); +opacity:0.3; +animation:ping 1.5s infinite; +} +.connection-status.connected{ +background:var(--accent); +opacity:0.8; +} +.connection-status.disconnected{ +background:var(--fg); +opacity:0.2; +} +@keyframes ping{ +0%,100%{opacity:0.3;transform:scale(0.8)} +50%{opacity:0.8;transform:scale(1.2)} +} +.markdown-content p{ +margin-bottom:12px; +line-height:1.7; +} +.markdown-content ul,.markdown-content ol{ +margin-bottom:12px; +padding-left:20px; +} +.markdown-content li{ +margin-bottom:4px; +} +.markdown-content code{ +background:var(--glass); +padding:2px 6px; +border-radius:4px; +font-family:monospace; +font-size:13px; +} +.markdown-content pre{ +border-radius:8px; +padding:12px; +overflow-x:auto; +margin-bottom:12px; +background:var(--glass); +border:1px solid var(--border); +} +.markdown-content pre code{ +background:none; +padding:0; +} +.markdown-content h1,.markdown-content h2,.markdown-content h3{ +margin-top:16px; +margin-bottom:8px; +font-weight:600; +} +.markdown-content h1{font-size:20px} +.markdown-content h2{font-size:18px} +.markdown-content h3{font-size:16px} +.markdown-content table{ +width:100%; +border-collapse:collapse; +margin-bottom:14px; +} +.markdown-content table th,.markdown-content table td{ +padding:8px; +text-align:left; +border:1px solid var(--border); +} +.markdown-content table th{ +font-weight:600; +background:var(--glass); +} +.markdown-content blockquote{ +border-left:2px solid var(--accent); +padding-left:14px; +margin:12px 0; +opacity:0.7; +font-style:italic; +} +.markdown-content a{ +color:var(--accent); +text-decoration:none; +transition:all 0.3s; +} +.markdown-content a:hover{ +opacity:0.7; +text-decoration:underline; +} +::-webkit-scrollbar{ +width:6px; +} +::-webkit-scrollbar-track{ +background:transparent; +} +::-webkit-scrollbar-thumb{ +background:var(--border); +border-radius:3px; +} +::-webkit-scrollbar-thumb:hover{ +background:var(--fg); +opacity:0.3; +} +@media(max-width:768px){ +.sidebar{ +width:100%; +left:-100%; +} +#messages{ +padding:20px 16px 140px; +} +.float-menu{ +left:12px; +top:12px; +} +.float-logo,.menu-button{ +width:36px; +height:36px; +font-size:14px; +} +.scroll-to-bottom{ +width:36px; +height:36px; +bottom:70px; +right:12px; +} +.context-indicator{ +bottom:120px; +right:12px; +width:100px; +} +} + -
-
- -
- - - -
- - - -
- - - - - +
+
+
+ + +
+ +
+ + +
+
Context
+
0%
+
+
+ +function toggleSidebar(){ +sidebar.classList.toggle('open'); +} + +function toggleTheme(){ +const themes=['auto','dark','light']; +const savedTheme=localStorage.getItem('gb-theme')||'auto'; +const idx=themes.indexOf(savedTheme); +const newTheme=themes[(idx+1)%themes.length]; +localStorage.setItem('gb-theme',newTheme); +currentTheme=newTheme; +applyTheme(); +updateThemeButton(); +} + +function updateThemeButton(){ +const icons={'auto':'⚙','dark':'🌙','light':'☀️'}; +themeBtn.textContent=icons[currentTheme]||'⚙'; +} + +function applyTheme(){ +const prefersDark=window.matchMedia('(prefers-color-scheme: dark)').matches; +let theme=currentTheme; +if(theme==='auto'){ +theme=prefersDark?'dark':'light'; +} +document.documentElement.setAttribute('data-theme',theme); +if(themeColor1&&themeColor2){ +const root=document.documentElement; +root.style.setProperty('--bg',theme==='dark'?themeColor2:themeColor1); +root.style.setProperty('--fg',theme==='dark'?themeColor1:themeColor2); +} +if(customLogoUrl){ +document.documentElement.style.setProperty('--logo-url',`url('${customLogoUrl}')`); +} +} + +window.addEventListener("load",function(){ +const savedTheme=localStorage.getItem('gb-theme')||'auto'; +currentTheme=savedTheme; +applyTheme(); +updateThemeButton(); +window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',()=>{ +if(currentTheme==='auto'){ +applyTheme(); +} +}); +input.focus(); +}); + +themeBtn.addEventListener('click',toggleTheme); + +document.addEventListener('click',function(e){ +if(sidebar.classList.contains('open')&&!sidebar.contains(e.target)&&!floatLogo.contains(e.target)){ +sidebar.classList.remove('open'); +} +}); + +messagesDiv.addEventListener('scroll',function(){ +const isAtBottom=messagesDiv.scrollHeight-messagesDiv.scrollTop<=messagesDiv.clientHeight+100; +if(!isAtBottom){ +isUserScrolling=true; +scrollToBottomBtn.classList.add('visible'); +}else{ +isUserScrolling=false; +scrollToBottomBtn.classList.remove('visible'); +} +}); + +scrollToBottomBtn.addEventListener('click',function(){ +scrollToBottom(); +}); + +function updateContextUsage(u){ +contextUsage=u; +const p=Math.min(100,Math.round(u*100)); +contextPercentage.textContent=`${p}%`; +contextProgressBar.style.width=`${p}%`; +contextIndicator.classList.remove('visible'); +} + +function flashScreen(){ +gsap.to(flashOverlay,{ +opacity:0.15, +duration:0.1, +onComplete:()=>{ +gsap.to(flashOverlay,{opacity:0,duration:0.2}); +} +}); +} + +function updateConnectionStatus(s){ +connectionStatus.className=`connection-status ${s}`; +} + +function getWebSocketUrl(){ +const p=window.location.protocol==="https:"?"wss:":"ws:",s=currentSessionId||crypto.randomUUID(),u=currentUserId||crypto.randomUUID(); +return`${p}//${window.location.host}/ws?session_id=${s}&user_id=${u}`; +} + +async function initializeAuth(){ +try{ +updateConnectionStatus("connecting"); +const p=window.location.pathname.split('/').filter(s=>s),b=p.length>0?p[0]:'default',r=await fetch(`/api/auth?bot_name=${encodeURIComponent(b)}`),a=await r.json(); +currentUserId=a.user_id; +currentSessionId=a.session_id; +connectWebSocket(); +loadSessions(); +}catch(e){ +console.error("Failed to initialize auth:",e); +updateConnectionStatus("disconnected"); +setTimeout(initializeAuth,3000); +} +} + +async function loadSessions(){ +try{ +const r=await fetch("/api/sessions"),s=await r.json(),h=document.getElementById("history"); +h.innerHTML=""; +s.forEach(session=>{ +const item=document.createElement('div'); +item.className='history-item'; +item.textContent=session.title||`Session ${session.session_id.substring(0,8)}`; +item.onclick=()=>switchSession(session.session_id); +h.appendChild(item); +}); +}catch(e){ +console.error("Failed to load sessions:",e); +} +} + +async function createNewSession(){ +try{ +const r=await fetch("/api/sessions",{method:"POST"}),s=await r.json(); +currentSessionId=s.session_id; +hasReceivedInitialMessage=false; +connectWebSocket(); +loadSessions(); +messagesDiv.innerHTML=""; +clearSuggestions(); +updateContextUsage(0); +if(isVoiceMode){ +await stopVoiceSession(); +isVoiceMode=false; +const v=document.getElementById("voiceToggle"); +v.textContent="🎤 Voice Mode"; +voiceBtn.classList.remove("recording"); +} +}catch(e){ +console.error("Failed to create session:",e); +} +} + +function switchSession(s){ +currentSessionId=s; +hasReceivedInitialMessage=false; +loadSessionHistory(s); +connectWebSocket(); +if(isVoiceMode){ +startVoiceSession(); +} +sidebar.classList.remove('open'); +} + +async function loadSessionHistory(s){ +try{ +const r=await fetch("/api/sessions/"+s),h=await r.json(),m=document.getElementById("messages"); +m.innerHTML=""; +if(h.length===0){ +updateContextUsage(0); +}else{ +h.forEach(([role,content])=>{ +addMessage(role,content,false); +}); +updateContextUsage(h.length/20); +} +}catch(e){ +console.error("Failed to load session history:",e); +} +} + +function connectWebSocket(){ +if(ws){ +ws.close(); +} +clearTimeout(reconnectTimeout); +const u=getWebSocketUrl(); +ws=new WebSocket(u); +ws.onmessage=function(e){ +const r=JSON.parse(e.data); +if(r.bot_id){ +currentBotId=r.bot_id; +} +if(r.message_type===2){ +const d=JSON.parse(r.content); +handleEvent(d.event,d.data); +return; +} +if(r.message_type===5){ +isContextChange=true; +return; +} +processMessageContent(r); +}; +ws.onopen=function(){ +console.log("Connected to WebSocket"); +updateConnectionStatus("connected"); +reconnectAttempts=0; +hasReceivedInitialMessage=false; +}; +ws.onclose=function(e){ +console.log("WebSocket disconnected:",e.code,e.reason); +updateConnectionStatus("disconnected"); +if(isStreaming){ +showContinueButton(); +} +if(reconnectAttempts{ +updateConnectionStatus("connecting"); +connectWebSocket(); +},d); +}else{ +updateConnectionStatus("disconnected"); +} +}; +ws.onerror=function(e){ +console.error("WebSocket error:",e); +updateConnectionStatus("disconnected"); +}; +} + +function processMessageContent(r){ +if(isContextChange){ +isContextChange=false; +return; +} +if(r.context_usage!==undefined){ +updateContextUsage(r.context_usage); +} +if(r.suggestions&&r.suggestions.length>0){ +handleSuggestions(r.suggestions); +} +if(r.is_complete){ +if(isStreaming){ +finalizeStreamingMessage(); +isStreaming=false; +streamingMessageId=null; +currentStreamingContent=""; +}else{ +addMessage("assistant",r.content,false); +} +}else{ +if(!isStreaming){ +isStreaming=true; +streamingMessageId="streaming-"+Date.now(); +currentStreamingContent=r.content||""; +addMessage("assistant",currentStreamingContent,true,streamingMessageId); +}else{ +currentStreamingContent+=r.content||""; +updateStreamingMessage(currentStreamingContent); +} +} +} + +function handleEvent(t,d){ +console.log("Event received:",t,d); +switch(t){ +case"thinking_start": +showThinkingIndicator(); +break; +case"thinking_end": +hideThinkingIndicator(); +break; +case"warn": +showWarning(d.message); +break; +case"context_usage": +updateContextUsage(d.usage); +break; +case"change_theme": +if(d.color1)themeColor1=d.color1; +if(d.color2)themeColor2=d.color2; +if(d.logo_url)customLogoUrl=d.logo_url; +if(d.title)document.title=d.title; +if(d.logo_text){ +sidebarTitle.textContent=d.logo_text; +} +applyTheme(); +break; +} +} + +function showThinkingIndicator(){ +if(isThinking)return; +const t=document.createElement("div"); +t.id="thinking-indicator"; +t.className="message-container"; +t.innerHTML=`
`; +messagesDiv.appendChild(t); +gsap.to(t,{opacity:1,y:0,duration:.3,ease:"power2.out"}); +if(!isUserScrolling){ +scrollToBottom(); +} +thinkingTimeout=setTimeout(()=>{ +if(isThinking){ +hideThinkingIndicator(); +showWarning("O servidor pode estar ocupado. A resposta está demorando demais."); +} +},60000); +isThinking=true; +} + +function hideThinkingIndicator(){ +if(!isThinking)return; +const t=document.getElementById("thinking-indicator"); +if(t){ +gsap.to(t,{opacity:0,duration:.2,onComplete:()=>{ +if(t.parentNode){ +t.remove(); +} +}}); +} +if(thinkingTimeout){ +clearTimeout(thinkingTimeout); +thinkingTimeout=null; +} +isThinking=false; +} + +function showWarning(m){ +const w=document.createElement("div"); +w.className="warning-message"; +w.innerHTML=`⚠️ ${m}`; +messagesDiv.appendChild(w); +gsap.from(w,{opacity:0,y:20,duration:.4,ease:"power2.out"}); +if(!isUserScrolling){ +scrollToBottom(); +} +setTimeout(()=>{ +if(w.parentNode){ +gsap.to(w,{opacity:0,duration:.3,onComplete:()=>w.remove()}); +} +},5000); +} + +function showContinueButton(){ +const c=document.createElement("div"); +c.className="message-container"; +c.innerHTML=`

A conexão foi interrompida. Clique em "Continuar" para tentar recuperar a resposta.

`; +messagesDiv.appendChild(c); +gsap.to(c,{opacity:1,y:0,duration:.5,ease:"power2.out"}); +if(!isUserScrolling){ +scrollToBottom(); +} +} + +function continueInterruptedResponse(){ +if(!ws||ws.readyState!==WebSocket.OPEN){ +connectWebSocket(); +} +if(ws&&ws.readyState===WebSocket.OPEN){ +const d={bot_id:"default_bot",user_id:currentUserId,session_id:currentSessionId,channel:"web",content:"continue",message_type:3,media_url:null,timestamp:new Date().toISOString()}; +ws.send(JSON.stringify(d)); +} +document.querySelectorAll(".continue-button").forEach(b=>{ +b.parentElement.parentElement.parentElement.remove(); +}); +} + +function addMessage(role,content,streaming=false,msgId=null){ +const m=document.createElement("div"); +m.className="message-container"; +if(role==="user"){ +m.innerHTML=`
${escapeHtml(content)}
`; +updateContextUsage(contextUsage+.05); +}else if(role==="assistant"){ +m.innerHTML=`
${streaming?"":marked.parse(content)}
`; +updateContextUsage(contextUsage+.03); +}else if(role==="voice"){ +m.innerHTML=`
🎤
${content}
`; +}else{ +m.innerHTML=`
${content}
`; +} +messagesDiv.appendChild(m); +gsap.to(m,{opacity:1,y:0,duration:.5,ease:"power2.out"}); +if(!isUserScrolling){ +scrollToBottom(); +} +} + +function updateStreamingMessage(c){ +const m=document.getElementById(streamingMessageId); +if(m){ +m.innerHTML=marked.parse(c); +if(!isUserScrolling){ +scrollToBottom(); +} +} +} + +function finalizeStreamingMessage(){ +const m=document.getElementById(streamingMessageId); +if(m){ +m.innerHTML=marked.parse(currentStreamingContent); +m.removeAttribute("id"); +if(!isUserScrolling){ +scrollToBottom(); +} +} +} + +function escapeHtml(t){ +const d=document.createElement("div"); +d.textContent=t; +return d.innerHTML; +} + +function clearSuggestions(){ +suggestionsContainer.innerHTML=''; +} + +function handleSuggestions(s){ +const uniqueSuggestions=s.filter((v,i,a)=>i===a.findIndex(t=>t.text===v.text&&t.context===v.context)); +suggestionsContainer.innerHTML=''; +uniqueSuggestions.forEach(v=>{ +const b=document.createElement('button'); +b.textContent=v.text; +b.className='suggestion-button'; +b.onclick=()=>{ +setContext(v.context); +input.value=''; +}; +suggestionsContainer.appendChild(b); +}); +} + +let pendingContextChange=null; + +async function setContext(c){ +try{ +const t=event?.target?.textContent||c; +addMessage("user",t); +const i=document.getElementById('messageInput'); +if(i){ +i.value=''; +} +if(ws&&ws.readyState===WebSocket.OPEN){ +pendingContextChange=new Promise(r=>{ +const h=e=>{ +const d=JSON.parse(e.data); +if(d.message_type===5&&d.context_name===c){ +ws.removeEventListener('message',h); +r(); +} +}; +ws.addEventListener('message',h); +const s={bot_id:currentBotId,user_id:currentUserId,session_id:currentSessionId,channel:"web",content:t,message_type:4,is_suggestion:true,context_name:c,timestamp:new Date().toISOString()}; +ws.send(JSON.stringify(s)); +}); +await pendingContextChange; +const x=document.getElementById('contextIndicator'); +if(x){ +document.getElementById('contextPercentage').textContent=c; +} +}else{ +console.warn("WebSocket não está conectado. Tentando reconectar..."); +connectWebSocket(); +} +}catch(err){ +console.error('Failed to set context:',err); +} +} + +async function sendMessage(){ +if(pendingContextChange){ +await pendingContextChange; +pendingContextChange=null; +} +const m=input.value.trim(); +if(!m||!ws||ws.readyState!==WebSocket.OPEN){ +if(!ws||ws.readyState!==WebSocket.OPEN){ +showWarning("Conexão não disponível. Tentando reconectar..."); +connectWebSocket(); +} +return; +} +if(isThinking){ +hideThinkingIndicator(); +} +addMessage("user",m); +const d={bot_id:currentBotId,user_id:currentUserId,session_id:currentSessionId,channel:"web",content:m,message_type:1,media_url:null,timestamp:new Date().toISOString()}; +ws.send(JSON.stringify(d)); +input.value=""; +input.focus(); +} + +sendBtn.onclick=sendMessage; +input.addEventListener("keypress",e=>{ +if(e.key==="Enter")sendMessage(); +}); + +async function toggleVoiceMode(){ +isVoiceMode=!isVoiceMode; +const v=document.getElementById("voiceToggle"); +if(isVoiceMode){ +v.textContent="🔴 Stop Voice"; +v.classList.add("recording"); +await startVoiceSession(); +}else{ +v.textContent="🎤 Voice Mode"; +v.classList.remove("recording"); +await stopVoiceSession(); +} +} + +async function startVoiceSession(){ +if(!currentSessionId)return; +try{ +const r=await fetch("/api/voice/start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:currentSessionId,user_id:currentUserId})}),d=await r.json(); +if(d.token){ +await connectToVoiceRoom(d.token); +startVoiceRecording(); +} +}catch(e){ +console.error("Failed to start voice session:",e); +showWarning("Falha ao iniciar modo de voz"); +} +} + +async function stopVoiceSession(){ +if(!currentSessionId)return; +try{ +await fetch("/api/voice/stop",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:currentSessionId})}); +if(voiceRoom){ +voiceRoom.disconnect(); +voiceRoom=null; +} +if(mediaRecorder&&mediaRecorder.state==="recording"){ +mediaRecorder.stop(); +} +}catch(e){ +console.error("Failed to stop voice session:",e); +} +} + +async function connectToVoiceRoom(t){ +try{ +const r=new LiveKitClient.Room(),p=window.location.protocol==="https:"?"wss:":"ws:",u=`${p}//${window.location.host}/voice`; +await r.connect(u,t); + +voiceRoom=r; +r.on("dataReceived",d=>{ +const dc=new TextDecoder(),m=dc.decode(d); +try{ +const j=JSON.parse(m); +if(j.type==="voice_response"){ +addMessage("assistant",j.text); +} +}catch(e){ +console.log("Voice data:",m); +} +}); +const l=await LiveKitClient.createLocalTracks({audio:true,video:false}); +for(const k of l){ +await r.localParticipant.publishTrack(k); +} +}catch(e){ +console.error("Failed to connect to voice room:",e); +showWarning("Falha na conexão de voz"); +} +} + +function startVoiceRecording(){ +if(!navigator.mediaDevices){ +console.log("Media devices not supported"); +return; +} +navigator.mediaDevices.getUserMedia({audio:true}).then(s=>{ +mediaRecorder=new MediaRecorder(s); +audioChunks=[]; +mediaRecorder.ondataavailable=e=>{ +audioChunks.push(e.data); +}; +mediaRecorder.onstop=()=>{ +const a=new Blob(audioChunks,{type:"audio/wav"}); +simulateVoiceTranscription(); +}; +mediaRecorder.start(); +setTimeout(()=>{ +if(mediaRecorder&&mediaRecorder.state==="recording"){ +mediaRecorder.stop(); +setTimeout(()=>{ +if(isVoiceMode){ +startVoiceRecording(); +} +},1000); +} +},5000); +}).catch(e=>{ +console.error("Error accessing microphone:",e); +showWarning("Erro ao acessar microfone"); +}); +} + +function simulateVoiceTranscription(){ +const p=["Olá, como posso ajudá-lo hoje?","Entendo o que você está dizendo","Esse é um ponto interessante","Deixe-me pensar sobre isso","Posso ajudá-lo com isso","O que você gostaria de saber?","Isso parece ótimo","Estou ouvindo sua voz"],r=p[Math.floor(Math.random()*p.length)]; +if(voiceRoom){ +const m={type:"voice_input",content:r,timestamp:new Date().toISOString()}; +voiceRoom.localParticipant.publishData(new TextEncoder().encode(JSON.stringify(m)),LiveKitClient.DataPacketKind.RELIABLE); +} +addMessage("voice",`🎤 ${r}`); +} + +function scrollToBottom(){ +messagesDiv.scrollTop=messagesDiv.scrollHeight; +isUserScrolling=false; +scrollToBottomBtn.classList.remove('visible'); +} + +window.addEventListener("load",initializeAuth); +window.addEventListener("focus",function(){ +if(!ws||ws.readyState!==WebSocket.OPEN){ +connectWebSocket(); +} +}); +