//alert(111) //test1 //test2 bottom right function createFloatChatWindow(baseOrOptions) { if (document.getElementById('float-chat-host')) return; // --- Options const opts = typeof baseOrOptions === 'string' ? { baseUrl: baseOrOptions } : (baseOrOptions || {}); const BASE_URL = (opts.baseUrl || '').replace(/\/+$/,''); const TITLE = opts.title || 'fcic AI'; const PLACEHOLDER = opts.placeholder || 'Type your message…'; const COLLAPSE_H = Number(opts.collapseHeight || 52); const ENABLE_DRAG = opts.enableDrag !== false; const CORS_PROXY = opts.corsProxy || null; const buildPath = typeof opts.queryPathBuilder === 'function' ? opts.queryPathBuilder : (q) => `/${encodeURIComponent(q)}`; // --- Icon pack (24x24 outline; currentColor) const ICON_PATHS = { chevronDown: ['M6 9l6 6 6-6'], chevronUp: ['M18 15l-6-6-6 6'], close: ['M6 6l12 12','M6 18L18 6'], plane: ['M22 2 11 13','M22 2 15 22l-4-9-9-4 20-7z'], copy: ['M9 9h8v8H9z','M5 5h8v8H5z'], bot: ['M9 8h6a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3v-2a3 3 0 0 1 3-3z','M11 4h2v3h-2z','M10 12h.01','M14 12h.01'], user: ['M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8','M4 20a8 8 0 0 1 16 0'] }; function makeIcon(name, size=18) { const svg = document.createElementNS('http://www.w3.org/2000/svg','svg'); svg.setAttribute('class','icon'); svg.setAttribute('viewBox','0 0 24 24'); svg.setAttribute('width', String(size)); svg.setAttribute('height', String(size)); svg.setAttribute('fill','none'); svg.setAttribute('stroke','currentColor'); svg.setAttribute('stroke-width','2'); svg.setAttribute('stroke-linecap','round'); svg.setAttribute('stroke-linejoin','round'); (ICON_PATHS[name]||[]).forEach(d=>{ const p=document.createElementNS('http://www.w3.org/2000/svg','path'); p.setAttribute('d',d); svg.appendChild(p); }); return svg; } function setIcon(el, name) { el.textContent = ''; el.appendChild(makeIcon(name, 18)); } // --- Host + Shadow const host = document.createElement('div'); host.id = 'float-chat-host'; document.body.appendChild(host); const shadow = host.attachShadow({ mode: 'open' }); // --- Styles const css = ` :host, #float-chat { all: initial; font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, "Helvetica Neue", sans-serif; } #float-chat { position:fixed; bottom:20px; right:20px; width:360px; height:500px; display:flex; flex-direction:column; background:#fff; border:1px solid rgba(15,23,42,.08); border-radius:16px; box-shadow:0 12px 32px rgba(0,0,0,.18); overflow:hidden; z-index:2147483647; transition:height .28s ease, width .28s ease, transform .2s ease, box-shadow .2s ease; backdrop-filter:saturate(1.1) blur(6px); } #float-chat.collapsed { height:${COLLAPSE_H}px; } #loading-bar{position:absolute;top:0;left:0;height:3px;width:0;opacity:0;background:linear-gradient(90deg,#6d5efc,#a78bfa,#6d5efc);background-size:200% 100%;} #float-chat.loading #loading-bar{opacity:1;animation:loadSlide 1.2s linear infinite;} @keyframes loadSlide{0%{width:0;background-position:0 0}50%{width:70%;background-position:100% 0}100%{width:100%;background-position:0 0}} #chat-header{ position:relative; display:flex; align-items:center; gap:8px; height:${COLLAPSE_H}px; padding:0 10px; color:#fff; background:linear-gradient(135deg,#6d5efc,#a78bfa); box-shadow:inset 0 -1px 0 rgba(255,255,255,.25); user-select:none; } .icon-btn{ background:transparent; border:none; color:inherit; font-size:0; cursor:pointer; line-height:0; padding:8px; border-radius:10px; display:inline-flex; align-items:center; justify-content:center; transition:background .15s ease, transform .12s ease; } .icon-btn:hover{ background:rgba(255,255,255,.15); } .icon{ display:block; width:18px; height:18px; } #title-wrap{ display:flex; align-items:center; gap:8px; flex:1; justify-content:center; } #title{ font-weight:800; font-size:15px; letter-spacing:.25px; text-shadow:0 1px 0 rgba(0,0,0,.2); } #history{ position:relative; flex:1; padding:12px; overflow-y:auto; background:#f6f7f9; } .row{ display:flex; gap:8px; margin:10px 0; align-items:flex-end; } .row.user{ justify-content:flex-end; } .row.ai { justify-content:flex-start; } .avatar{ width:28px; height:28px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; color:#fff; flex:0 0 auto; box-shadow:0 2px 6px rgba(0,0,0,.12); } .avatar.ai { background:linear-gradient(135deg,#38bdf8,#818cf8); } .avatar.user { background:darkgreen } .bubble{ position:relative; max-width:74%; padding:10px 12px; border-radius:14px; font-size:14px; line-height:1.45; box-shadow:0 2px 8px rgba(0,0,0,.08); white-space:pre-wrap; word-wrap:break-word; animation:enter .2s ease; } @keyframes enter{ from{ transform:translateY(8px) scale(.98); opacity:0 } to{ transform:translateY(0) scale(1); opacity:1 } } .row.user .bubble{ color:#fff; background:linear-gradient(135deg,#6366f1,#22d3ee); border-bottom-right-radius:6px; } .row.ai .bubble { background:#fff; color:#0f172a; border:1px solid rgba(0,0,0,.06); border-bottom-left-radius:6px; } .timestamp{ font-size:10px; color:#8e9aa6; margin-top:4px; text-align:right; } .row.ai .timestamp{ text-align:left; } .tools{ position:absolute; top:6px; right:6px; display:flex; gap:6px; opacity:0; transition:opacity .15s ease; } .row.ai .bubble:hover .tools{ opacity:1; } .tool{ background:rgba(0,0,0,.06); border:1px solid rgba(0,0,0,.08); width:22px; height:22px; border-radius:7px; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; color:#0f172a; } .tool:hover{ filter:brightness(1.04); } #controls{ display:flex; gap:8px; padding:10px; background:#eef1f5; border-top:1px solid #e6e8ec; } #input{ flex:1; height:40px; padding:0 12px; border:1px solid #cfd6df; border-radius:12px; font-size:14px; outline:none; background:#fff; } #send{ padding:0 14px; min-width:44px; border:none; border-radius:12px; background:#22c55e; color:#fff; font-weight:700; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; gap:6px; height:40px; } #send[disabled]{ opacity:.6; cursor:not-allowed; } .typing{ display:inline-flex; align-items:center; gap:6px; height:1em; } .dot{ width:6px; height:6px; background:#94a3b8; border-radius:50%; animation:bounce 1s infinite ease-in-out; } .dot:nth-child(2){ animation-delay:.15s; } .dot:nth-child(3){ animation-delay:.3s; } @keyframes bounce{ 0%,80%,100%{ transform:scale(.6); opacity:.6 } 40%{ transform:scale(1); opacity:1 } } `; const styleEl = document.createElement('style'); styleEl.textContent = css; shadow.appendChild(styleEl); // --- Structure const container = document.createElement('div'); container.id = 'float-chat'; const loadingBar = document.createElement('div'); loadingBar.id = 'loading-bar'; container.appendChild(loadingBar); const header = document.createElement('div'); header.id = 'chat-header'; const toggleBtn = document.createElement('button'); toggleBtn.className = 'icon-btn'; toggleBtn.setAttribute('aria-label','Collapse/Expand'); setIcon(toggleBtn, 'chevronDown'); const titleWrap = document.createElement('div'); titleWrap.id = 'title-wrap'; const brand = makeIcon('bot', 18); const title = document.createElement('div'); title.id = 'title'; title.textContent = TITLE; titleWrap.appendChild(brand); titleWrap.appendChild(title); const closeBtn = document.createElement('button'); closeBtn.className = 'icon-btn'; closeBtn.setAttribute('aria-label','Close'); setIcon(closeBtn, 'close'); header.appendChild(toggleBtn); header.appendChild(titleWrap); header.appendChild(closeBtn); const history = document.createElement('div'); history.id = 'history'; const controls = document.createElement('div'); controls.id = 'controls'; const input = document.createElement('input'); input.id = 'input'; input.type = 'text'; input.placeholder = PLACEHOLDER; input.autocomplete = 'off'; input.spellcheck = false; const sendBtn = document.createElement('button'); sendBtn.id = 'send'; sendBtn.appendChild(makeIcon('plane', 18)); sendBtn.appendChild(document.createTextNode('Send')); controls.appendChild(input); controls.appendChild(sendBtn); container.appendChild(header); container.appendChild(history); container.appendChild(controls); shadow.appendChild(container); // --- Helpers function nowStamp(){ try { return new Date().toLocaleString(); } catch { return ''; } } function makeRow(kind){ const row=document.createElement('div'); row.className=`row ${kind}`; const avatar=document.createElement('div'); avatar.className=`avatar ${kind}`; avatar.appendChild(makeIcon(kind === 'ai' ? 'bot' : 'user', 16)); // bubble + timestamp const wrap=document.createElement('div'); wrap.style.display='flex'; wrap.style.flexDirection='column'; const bubble=document.createElement('div'); bubble.className='bubble'; if (kind==='ai') { const tools = document.createElement('div'); tools.className = 'tools'; const copyBtn = document.createElement('div'); copyBtn.className='tool'; copyBtn.title='Copy'; copyBtn.appendChild(makeIcon('copy', 16)); copyBtn.addEventListener('click', async ()=> { try { await navigator.clipboard.writeText(bubble.textContent || ''); } catch {} }); tools.appendChild(copyBtn); bubble.appendChild(tools); } const ts=document.createElement('div'); ts.className='timestamp'; ts.textContent=nowStamp(); wrap.appendChild(bubble); wrap.appendChild(ts); // Order per side if (kind==='ai') { row.appendChild(avatar); row.appendChild(wrap); } else { row.appendChild(wrap); row.appendChild(avatar); } return {row,bubble}; } function appendUser(text){ const {row,bubble}=makeRow('user'); bubble.textContent=text; history.appendChild(row); history.scrollTop=history.scrollHeight; } function appendTyping(){ const {row,bubble}=makeRow('ai'); const t=document.createElement('span'); t.className='typing'; t.innerHTML=''; bubble.appendChild(t); row.dataset.pending='1'; history.appendChild(row); history.scrollTop=history.scrollHeight; return {row,bubble}; } function replaceTypingWithText(ref,text){ if(!ref||!ref.bubble) return; ref.bubble.childNodes.forEach(n => { if (n.nodeType===1 && n.classList && n.classList.contains('typing')) n.remove(); }); const tools = ref.bubble.querySelector('.tools'); // preserve tools ref.bubble.textContent = text; if (tools) ref.bubble.appendChild(tools); ref.row && (ref.row.dataset.pending=''); history.scrollTop=history.scrollHeight; } function setSending(on){ if(on){ container.classList.add('loading'); sendBtn.disabled=true; } else { container.classList.remove('loading'); sendBtn.disabled=false; } } function buildFetchUrl(q){ let u = BASE_URL + buildPath(q); if (CORS_PROXY) u = `${CORS_PROXY}${encodeURIComponent(u)}`; return u; } async function tryStreamResponse(res,el){ try{ if(!res.body || !('getReader' in res.body)) return false; const r=res.body.getReader(); const dec=new TextDecoder(); let buf=''; while(true){ const {value,done}=await r.read(); if(done) break; buf+=dec.decode(value,{stream:true}); el.textContent=buf; history.scrollTop=history.scrollHeight; } el.textContent=buf+dec.decode(); return true; }catch{ return false; } } async function sendMessage(){ const q=(input.value||'').trim(); if(!q || !BASE_URL) return; appendUser(q); input.value=''; input.focus(); const typing=appendTyping(); setSending(true); try{ const res=await fetch(buildFetchUrl(q),{method:'GET'}); if(!res.ok){ const txt=await res.text().catch(()=> ''); replaceTypingWithText(typing,`Error ${res.status}: ${txt||res.statusText}`); return; } const streamed=await tryStreamResponse(res.clone(), typing.bubble); if(!streamed){ const data=await res.text(); replaceTypingWithText(typing,data); } }catch(err){ replaceTypingWithText(typing,`Error: ${err?.message || String(err)}`); } finally{ setSending(false); } } // --- Events const applyChevron = () => { setIcon(toggleBtn, container.classList.contains('collapsed') ? 'chevronUp' : 'chevronDown'); }; function usingTopLeftAnchor(){ const hasTop = container.style.top && container.style.top !== ''; const rightUnset = container.style.right === 'unset'; return hasTop || rightUnset; } function toggleCollapsed(){ const beforeH = container.getBoundingClientRect().height; const anchoredTop = usingTopLeftAnchor(); const currentTop = anchoredTop ? parseFloat(container.style.top || '0') : null; container.classList.toggle('collapsed'); const afterH = container.getBoundingClientRect().height; if (anchoredTop && Number.isFinite(currentTop)) { const delta = beforeH - afterH; container.style.top = Math.max(8, currentTop + delta) + 'px'; } applyChevron(); } sendBtn.addEventListener('click', sendMessage); input.addEventListener('keydown', (e)=>{ if(e.key==='Enter'){ e.preventDefault(); sendMessage(); } }); toggleBtn.addEventListener('click', (e)=>{ e.stopPropagation(); toggleCollapsed(); }); header.addEventListener('click', (e)=>{ if (e.target.closest('.icon-btn')) return; toggleCollapsed(); }); closeBtn.addEventListener('click', (e)=>{ e.stopPropagation(); host.remove(); }); // Drag (keeps bottom anchor when collapsing) if (ENABLE_DRAG) { let startX=0, startY=0, startLeft=0, startTop=0, dragging=false; const onMove = (e) => { if (!dragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; container.style.right = 'unset'; container.style.left = Math.max(8, startLeft + dx) + 'px'; container.style.top = Math.max(8, startTop + dy) + 'px'; }; const onUp = () => { dragging = false; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); container.style.transition = 'height .28s ease, width .28s ease, transform .2s ease, box-shadow .2s ease'; }; header.addEventListener('mousedown', (e) => { if (e.target.closest('.icon-btn')) return; dragging = true; const rect = container.getBoundingClientRect(); startX = e.clientX; startY = e.clientY; startLeft = rect.left; startTop = rect.top; container.style.transition = 'none'; window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp); }); } // Focus on open setTimeout(()=> input.focus(), 0); } // Use it createFloatChatWindow("https://advice.us.kg"); //test3 top right function createAdvancedFloatChatWindow(baseUrl, prompt, pollInterval = 10, displayMode = 'S', speed = 50) { // Use a unique host ID to avoid conflicts const hostId = 'advanced-float-chat-host'; const existingHost = document.getElementById(hostId); if (existingHost) { if (existingHost.pollTimer) { clearInterval(existingHost.pollTimer); } existingHost.remove(); } // 1. Create a new host element and attach a shadow root const host = document.createElement('div'); host.id = hostId; document.body.appendChild(host); const shadow = host.attachShadow({ mode: 'open' }); const basePixelSpeed = 30 * (speed / 50); // pixels per second at given speed // 2. Inject minimal CSS for the container and display const css = ` #float-chat-container { position: fixed; top: 66px; right: 20px; width: 320px; /* Adjust height based on displayMode */ height: ${displayMode === 'M' ? '120px' : 'auto'}; /* Assuming approx 24px per line * 5 lines */ border: 1px solid #ddd; border-radius: 8px; background: #fff; font-family: "Helvetica Neue", Arial, sans-serif; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); z-index: 2147483647; cursor: move; padding: ${displayMode === 'M' ? '0' : '8px 12px'}; display: flex; align-items: center; overflow: hidden; } #flu-display { font-size: 14px; color: #333; display: ${displayMode === 'S' ? 'inline-block' : 'block'}; ${displayMode === 'S' ? `white-space: nowrap;` : `white-space: pre-wrap; line-height: 24px;` /* Adjust line-height as needed */ } } @keyframes scrollText { 0% { transform: translateX(0); } 100% { transform: translateX(-100%); } } @keyframes scrollTextVertical { 0% { transform: translateY(0); } 100% { transform: translateY(-50%); } } /* Smooth animation for vertical scrolling */ #flu-display.scroll-vertical { animation-timing-function: linear; animation-iteration-count: infinite; } /* Smooth animation for horizontal scrolling */ #flu-display.scroll-horizontal { animation-timing-function: linear; animation-iteration-count: infinite; } `; const styleEl = document.createElement('style'); styleEl.textContent = css; shadow.appendChild(styleEl); // 3. Create the floating container and display area const container = document.createElement('div'); container.id = 'float-chat-container'; const fluDisplay = document.createElement('div'); fluDisplay.id = 'flu-display'; fluDisplay.innerText = 'Loading...'; container.appendChild(fluDisplay); shadow.appendChild(container); // Polling control functions let pollTimer = null; function startPolling() { pollTimer = setInterval(fetchResponse, pollInterval * 1000); host.pollTimer = pollTimer; } function stopPolling() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; host.pollTimer = null; } } // 4. Polling logic function fetchResponse() { const queryParam = Array.isArray(prompt) ? prompt.join(' ') : prompt; fetch(`${baseUrl}/${encodeURIComponent(queryParam)}`) .then(response => response.text()) .then(text => { container.title = text; // Tooltip with full text if (displayMode === 'S') { const singleLine = text.replace(/\r?\n/g, ' '); fluDisplay.innerText = singleLine; // Immediately show text at left fluDisplay.style.transform = 'translateX(0)'; // Now calculate and start the horizontal animation requestAnimationFrame(() => { const textWidth = fluDisplay.scrollWidth; const containerWidth = container.offsetWidth; const totalDistance = textWidth + containerWidth; const newDuration = totalDistance / basePixelSpeed; // Remove previous animation classes fluDisplay.classList.remove('scroll-vertical'); fluDisplay.classList.add('scroll-horizontal'); // Apply the animation fluDisplay.style.animation = `scrollText ${newDuration}s linear infinite`; }); } else { // For multi-line vertical scrolling: limit to 5 lines and duplicate for seamless loop const lines = text.split(/\r?\n/).slice(0, 5); const limitedContent = lines.join('\n'); fluDisplay.innerText = limitedContent + '\n' + limitedContent; // Immediately show text at top fluDisplay.style.transform = 'translateY(0)'; // Now calculate and start the vertical animation requestAnimationFrame(() => { const textHeight = fluDisplay.scrollHeight / 2; // Since content is duplicated const newDuration = textHeight / basePixelSpeed; // Remove previous animation classes fluDisplay.classList.remove('scroll-horizontal'); fluDisplay.classList.add('scroll-vertical'); // Apply the animation fluDisplay.style.animation = `scrollTextVertical ${newDuration}s linear infinite`; }); } }) .catch(err => { const errorMsg = 'Error: ' + err.message; fluDisplay.innerText = errorMsg; container.title = errorMsg; }); } fetchResponse(); startPolling(); let offsetX = 0, offsetY = 0; let isDragging = false; container.addEventListener('mousedown', (e) => { isDragging = true; const rect = container.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; container.style.right = 'auto'; container.style.bottom = 'auto'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const x = e.clientX - offsetX; const y = e.clientY - offsetY; container.style.left = x + 'px'; container.style.top = y + 'px'; }); document.addEventListener('mouseup', () => { isDragging = false; }); container.addEventListener('mouseenter', () => { fluDisplay.style.animationPlayState = 'paused'; stopPolling(); }); container.addEventListener('mouseleave', () => { fluDisplay.style.animationPlayState = 'running'; startPolling(); }); // Optional cleanup/destroy function // return function destroy() { // stopPolling(); // host.remove(); // }; } setTimeout(x=> createAdvancedFloatChatWindow( "https://advice.us.kg", // baseUrl "tell me somethings about solar panel system in 50 words", // prompt 600, // poll every 20 seconds 'S', // multi-line mode (anything not 'S') 50 // speed ) ,3000 )