Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions scripts/docs-site/assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ function addChatThinking(){const log=document.querySelector("[data-chat-log]");i
function setChatClearVisible(visible){for(const selector of ["[data-chat-clear]","[data-chat-copy]"]){const btn=document.querySelector(selector);if(!btn)continue;if(visible)btn.removeAttribute("hidden");else btn.setAttribute("hidden","")}}
function clearChat(){const log=document.querySelector("[data-chat-log]");if(!log)return;log.innerHTML=chatEmptyHtml;log.scrollTop=0;setChatClearVisible(false);document.querySelector("[data-chat-input]")?.focus()}
function isAuthFetchError(err){const msg=String(err?.message||err||"").toLowerCase();return msg==="auth_required"||msg.includes("load failed")||msg.includes("failed to fetch")||msg.includes("networkerror")}
function chatFailureMessage(message){const msg=String(message||"").trim();const serverError=msg.toLowerCase().startsWith("docs agent returned 5");if(serverError)return"Molty is temporarily unavailable. Try again in a moment.";return msg||"Docs agent failed."}
const pendingMoltyQuestionKey="openclaw-docs-pending-molty-question";
function chatSignInUrl(){const url=new URL("https://hub.openclaw.ai/docs/auth");const back=new URL(location.href);if(localStorage.getItem(pendingMoltyQuestionKey))back.searchParams.set("molty_resume","1");url.searchParams.set("return_to",back.href);return url.href}
function initChat(){
Expand All @@ -124,8 +125,8 @@ const chatBaseRight=()=>window.matchMedia("(max-width:820px)").matches?14:18;let
const maximizeIcon='<svg class="icon icon-maximize-2" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="m21 3-7 7"/><path d="M9 21H3v-6"/><path d="m3 21 7-7"/></svg>';const collapseIcon='<svg class="icon icon-minimize-2" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m14 10 7-7"/><path d="M20 10h-6V4"/><path d="m3 21 7-7"/><path d="M4 14h6v6"/></svg>';const keepChatInViewport=()=>{if(!chat.classList.contains("open")){chatPanelRight=chatBaseRight();chat.style.left="";chat.style.right=chatPanelRight+"px";return}applyChatRight(chatPanelRight,chat.getBoundingClientRect().width)};const setExpanded=expanded=>{const targetWidth=chatTargetWidth(expanded);applyChatRight(chatPanelRight,targetWidth);chat.classList.toggle("expanded",expanded);if(maximize){maximize.setAttribute("aria-pressed",String(expanded));maximize.setAttribute("aria-label",expanded?"Restore docs assistant size":"Maximize docs assistant");maximize.innerHTML=expanded?collapseIcon:maximizeIcon}};window.addEventListener("resize",()=>keepChatInViewport());
let resetChatTimer=0;const resetChatLauncherPosition=()=>{chatPanelRight=chatBaseRight();chat.style.left="";chat.style.right=chatPanelRight+"px";chat.classList.remove("closing")};const setOpen=open=>{clearTimeout(resetChatTimer);chat.classList.toggle("open",open);if(open){chat.classList.remove("closing");applyChatRight(chatPanelRight,chat.getBoundingClientRect().width)}else{chat.classList.add("closing");resetChatTimer=setTimeout(resetChatLauncherPosition,220)}panel?.toggleAttribute("inert",!open);panel?.setAttribute("aria-hidden",String(!open));document.querySelector("[data-chat-toggle]")?.setAttribute("aria-expanded",String(open));if(open)ensureAuth().then(ok=>{if(ok)setTimeout(()=>input.focus(),0)})};
const chatText=()=>[...document.querySelectorAll(".docs-chat-message")].filter(item=>!item.classList.contains("thinking")).map(item=>(item.classList.contains("user")?"You: ":"OpenClaw: ")+item.innerText.trim()).filter(Boolean).join("\\n\\n");
const chatErrorMessage=async res=>{const fallback="Docs agent returned "+res.status;try{const data=await res.clone().json();return typeof data?.error==="string"&&data.error.trim()?data.error.trim():fallback}catch{return fallback}};
const sendChatMessage=async(message,{echoUser=true}={})=>{const api=window.OPENCLAW_DOCS_CHAT_API;if(!api||!message)return;if(!await ensureAuth())return;lastMessage=message;setRetryEnabled();if(echoUser)addChatMessage("user",message);setChatClearVisible(true);const reply=addChatThinking();if(submit)submit.disabled=true;if(retry)retry.disabled=true;try{const res=await fetch(api,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"same-origin",redirect:"manual",body:JSON.stringify({message,retrieval:"auto",confidenceThreshold:.3})});if(res.type==="opaqueredirect"||res.redirected||res.status===0||res.status===401||res.status===403)throw new Error("AUTH_REQUIRED");if(!res.ok)throw new Error(await chatErrorMessage(res));if(!res.body)throw new Error("Docs agent did not stream a response");const reader=res.body.getReader();const decoder=new TextDecoder();let raw="";while(true){const {done,value}=await reader.read();if(done)break;raw+=decoder.decode(value,{stream:true});if(reply&&raw.trim()){if(reply.classList.contains("thinking"))reply.classList.remove("thinking");reply.innerHTML=renderChatText(raw);reply.parentElement.scrollTop=reply.parentElement.scrollHeight}}if(!raw&&reply){reply.classList.remove("thinking");reply.innerHTML="<p>No response.</p>"}}catch(err){if(reply){const msg=isAuthFetchError(err)?"[Verify with GitHub]("+chatSignInUrl()+"), then ask again.":err?.message||"Docs agent failed.";reply.className="docs-chat-message error";reply.innerHTML=renderChatText(msg);if(isAuthFetchError(err))setAuthState("required")}}finally{if(submit)submit.disabled=authState!=="ready";setRetryEnabled();if(authState==="ready")input.focus()}};
const chatErrorMessage=async res=>{const fallback="Docs agent returned "+res.status;if(res.status>=500)return fallback;try{const data=await res.clone().json();return typeof data?.error==="string"&&data.error.trim()?data.error.trim():fallback}catch{return fallback}};
const sendChatMessage=async(message,{echoUser=true}={})=>{const api=window.OPENCLAW_DOCS_CHAT_API;if(!api||!message)return;if(!await ensureAuth())return;lastMessage=message;setRetryEnabled();if(echoUser)addChatMessage("user",message);setChatClearVisible(true);const reply=addChatThinking();if(submit)submit.disabled=true;if(retry)retry.disabled=true;try{const res=await fetch(api,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"same-origin",redirect:"manual",body:JSON.stringify({message,retrieval:"auto",confidenceThreshold:.3})});if(res.type==="opaqueredirect"||res.redirected||res.status===0||res.status===401||res.status===403)throw new Error("AUTH_REQUIRED");if(!res.ok)throw new Error(await chatErrorMessage(res));if(!res.body)throw new Error("Docs agent did not stream a response");const reader=res.body.getReader();const decoder=new TextDecoder();let raw="";while(true){const {done,value}=await reader.read();if(done)break;raw+=decoder.decode(value,{stream:true});if(reply&&raw.trim()){if(reply.classList.contains("thinking"))reply.classList.remove("thinking");reply.innerHTML=renderChatText(raw);reply.parentElement.scrollTop=reply.parentElement.scrollHeight}}if(!raw&&reply){reply.classList.remove("thinking");reply.innerHTML="<p>No response.</p>"}}catch(err){if(reply){const msg=isAuthFetchError(err)?"[Verify with GitHub]("+chatSignInUrl()+"), then ask again.":chatFailureMessage(err?.message);reply.className="docs-chat-message error";reply.innerHTML=renderChatText(msg);if(isAuthFetchError(err))setAuthState("required")}}finally{if(submit)submit.disabled=authState!=="ready";setRetryEnabled();if(authState==="ready")input.focus()}};
const askMoltyQuestion=async message=>{const question=String(message||"").trim();setOpen(true);if(!question){localStorage.removeItem(pendingMoltyQuestionKey);await ensureAuth();return}localStorage.setItem(pendingMoltyQuestionKey,question);if(!await ensureAuth())return;localStorage.removeItem(pendingMoltyQuestionKey);await sendChatMessage(question)};
window.OPENCLAW_DOCS_ASK_MOLTY=askMoltyQuestion;
const resumeMolty=new URLSearchParams(location.search).get("molty_resume")==="1";const pendingMoltyQuestion=localStorage.getItem(pendingMoltyQuestionKey);if(resumeMolty&&pendingMoltyQuestion){const clean=new URL(location.href);clean.searchParams.delete("molty_resume");history.replaceState(history.state,"",clean.href);setTimeout(()=>askMoltyQuestion(pendingMoltyQuestion),0)}
Expand Down
4 changes: 4 additions & 0 deletions scripts/docs-site/smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ if (/data-locale/.test(siteJs)) {
if (!/function initChat/.test(siteJs)
|| !/data-chat-form/.test(siteJs)
|| !/chat\.dataset\.chatAuthState=state/.test(siteJs)
|| !/function chatFailureMessage/.test(siteJs)
|| !/serverError=msg\.toLowerCase\(\)\.startsWith\("docs agent returned 5"\)/.test(siteJs)
|| !/if\(res\.status>=500\)return fallback/.test(siteJs)
|| !/Molty is temporarily unavailable\. Try again in a moment\./.test(siteJs)
|| !/panel\?\.toggleAttribute\("inert",!open\)/.test(siteJs)
|| !/panel\?\.setAttribute\("aria-hidden",String\(!open\)\)/.test(siteJs)
|| !/form\.inert=!ready/.test(siteJs)
Expand Down
4 changes: 4 additions & 0 deletions scripts/docs-site/visual-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ async function checkDesktop() {
throw new Error(`search shortcut inline hint failed: ${JSON.stringify(searchShortcut)}`);
}
await page.keyboard.press(process.platform === "darwin" ? "Meta+K" : "Control+K");
await page.waitForFunction(() =>
document.querySelector(".search-modal")?.classList.contains("open")
&& document.activeElement?.matches("[data-search-input]")
);
const searchOpen = await page.evaluate(() => ({
open: document.querySelector(".search-modal")?.classList.contains("open"),
focused: document.activeElement?.matches("[data-search-input]"),
Expand Down