mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 02:10:21 +00:00
feat: enhance chat widget with markdown rendering and style updates
- Integrated dynamic loading of markdown rendering for chat responses. - Implemented a fallback for markdown rendering to ensure consistent display. - Updated CSS variables for improved theming and visual consistency. - Enhanced chat bubble and input styles for better user experience. - Added new styles for markdown content in chat bubbles, including code blocks and lists.
This commit is contained in:
@@ -3,26 +3,51 @@
|
||||
|
||||
const apiBase = window.DOCS_CHAT_API_URL || "http://localhost:3001";
|
||||
|
||||
// Load @create-markdown/preview for markdown rendering
|
||||
let markdownToHTML = null;
|
||||
import("https://esm.sh/@create-markdown/preview@0.1.0")
|
||||
.then((mod) => {
|
||||
markdownToHTML = mod.markdownToHTML;
|
||||
})
|
||||
.catch((err) => console.warn("Failed to load create-markdown:", err));
|
||||
|
||||
// Markdown renderer with fallback before module loads
|
||||
const renderMarkdown = (text) => {
|
||||
if (markdownToHTML) {
|
||||
return markdownToHTML(text, { sanitize: true, linkTarget: "_blank" });
|
||||
}
|
||||
// Fallback: escape HTML and preserve newlines
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\n/g, "<br>");
|
||||
};
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
#docs-chat-root { position: fixed; right: 20px; bottom: 20px; z-index: 9999; font-family: inherit; }
|
||||
#docs-chat-root { position: fixed; right: 20px; bottom: 20px; z-index: 9999; font-family: var(--font-body, system-ui, -apple-system, sans-serif); }
|
||||
#docs-chat-root.docs-chat-expanded { right: 0; bottom: 0; }
|
||||
:root {
|
||||
--docs-chat-accent: #FF5A36;
|
||||
--docs-chat-text: #121212;
|
||||
--docs-chat-muted: #4b4b4b;
|
||||
--docs-chat-panel: rgba(255, 255, 255, 0.78);
|
||||
--docs-chat-panel-border: rgba(17, 17, 17, 0.08);
|
||||
--docs-chat-surface: rgba(255, 255, 255, 0.6);
|
||||
--docs-chat-shadow: 0 18px 50px rgba(0,0,0,0.18);
|
||||
--docs-chat-accent: var(--accent, #FF5A36);
|
||||
--docs-chat-text: #1a1a1a;
|
||||
--docs-chat-muted: #555;
|
||||
--docs-chat-panel: rgba(255, 255, 255, 0.92);
|
||||
--docs-chat-panel-border: rgba(0, 0, 0, 0.1);
|
||||
--docs-chat-surface: rgba(250, 250, 250, 0.95);
|
||||
--docs-chat-shadow: 0 18px 50px rgba(0,0,0,0.15);
|
||||
--docs-chat-code-bg: rgba(0, 0, 0, 0.05);
|
||||
--docs-chat-assistant-bg: #f5f5f5;
|
||||
}
|
||||
html[data-theme="dark"] {
|
||||
--docs-chat-text: #ececec;
|
||||
--docs-chat-muted: #b7b7b7;
|
||||
--docs-chat-panel: rgba(20, 20, 20, 0.78);
|
||||
--docs-chat-panel-border: rgba(255, 255, 255, 0.1);
|
||||
--docs-chat-surface: rgba(24, 24, 24, 0.65);
|
||||
--docs-chat-shadow: 0 18px 50px rgba(0,0,0,0.45);
|
||||
--docs-chat-text: #e8e8e8;
|
||||
--docs-chat-muted: #aaa;
|
||||
--docs-chat-panel: rgba(28, 28, 30, 0.95);
|
||||
--docs-chat-panel-border: rgba(255, 255, 255, 0.12);
|
||||
--docs-chat-surface: rgba(38, 38, 40, 0.95);
|
||||
--docs-chat-shadow: 0 18px 50px rgba(0,0,0,0.5);
|
||||
--docs-chat-code-bg: rgba(255, 255, 255, 0.08);
|
||||
--docs-chat-assistant-bg: #2a2a2c;
|
||||
}
|
||||
#docs-chat-button {
|
||||
display: inline-flex;
|
||||
@@ -37,8 +62,9 @@ html[data-theme="dark"] {
|
||||
box-shadow: 0 8px 30px rgba(255,90,54, 0.08);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
font-family: var(--font-pixel, var(--font-body, system-ui, sans-serif));
|
||||
}
|
||||
#docs-chat-button span { font-weight: 600; letter-spacing: 0.2px; }
|
||||
#docs-chat-button span { font-weight: 600; letter-spacing: 0.04em; font-size: 14px; }
|
||||
.docs-chat-logo { width: 20px; height: 20px; }
|
||||
#docs-chat-panel {
|
||||
width: 360px;
|
||||
@@ -62,13 +88,15 @@ html[data-theme="dark"] {
|
||||
#docs-chat-header {
|
||||
padding: 12px 14px;
|
||||
font-weight: 600;
|
||||
font-family: var(--font-pixel, var(--font-body, system-ui, sans-serif));
|
||||
letter-spacing: 0.03em;
|
||||
border-bottom: 1px solid var(--docs-chat-panel-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
#docs-chat-header-title { display: inline-flex; align-items: center; gap: 8px; }
|
||||
#docs-chat-header-title span { color: var(--docs-chat-text); }
|
||||
#docs-chat-header-title span { color: var(--docs-chat-text); font-size: 15px; }
|
||||
#docs-chat-header-actions { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.docs-chat-icon-button {
|
||||
border: 1px solid var(--docs-chat-panel-border);
|
||||
@@ -97,40 +125,117 @@ html[data-theme="dark"] {
|
||||
border: 1px solid var(--docs-chat-panel-border);
|
||||
border-radius: 10px;
|
||||
padding: 9px 10px;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-family: inherit;
|
||||
color: var(--docs-chat-text);
|
||||
background: rgba(255,255,255,0.7);
|
||||
background: var(--docs-chat-surface);
|
||||
min-height: 42px;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
html[data-theme="dark"] #docs-chat-input textarea { background: rgba(15,15,15,0.7); }
|
||||
#docs-chat-input textarea::placeholder { color: var(--docs-chat-muted); }
|
||||
#docs-chat-send {
|
||||
background: var(--docs-chat-accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
#docs-chat-send:hover { opacity: 0.9; }
|
||||
#docs-chat-send:active { opacity: 0.8; }
|
||||
.docs-chat-bubble {
|
||||
margin-bottom: 10px;
|
||||
padding: 9px 12px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
max-width: 90%;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
max-width: 92%;
|
||||
}
|
||||
.docs-chat-user {
|
||||
background: var(--docs-chat-accent);
|
||||
color: #fff;
|
||||
align-self: flex-end;
|
||||
white-space: pre-wrap;
|
||||
margin-left: auto;
|
||||
}
|
||||
.docs-chat-assistant {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
background: var(--docs-chat-assistant-bg);
|
||||
color: var(--docs-chat-text);
|
||||
border: 1px solid var(--docs-chat-panel-border);
|
||||
}
|
||||
html[data-theme="dark"] .docs-chat-assistant { background: rgba(20, 20, 20, 0.7); }
|
||||
/* Markdown content styling for chat bubbles */
|
||||
.docs-chat-assistant p { margin: 0 0 10px 0; }
|
||||
.docs-chat-assistant p:last-child { margin-bottom: 0; }
|
||||
.docs-chat-assistant code {
|
||||
background: var(--docs-chat-code-bg);
|
||||
padding: 2px 6px;
|
||||
border-radius: 5px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.docs-chat-assistant pre {
|
||||
background: var(--docs-chat-code-bg);
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin: 10px 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.docs-chat-assistant pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
.docs-chat-assistant a {
|
||||
color: var(--docs-chat-accent);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.docs-chat-assistant a:hover { opacity: 0.8; }
|
||||
.docs-chat-assistant ul {
|
||||
margin: 8px 0;
|
||||
padding-left: 22px;
|
||||
list-style-type: disc;
|
||||
}
|
||||
.docs-chat-assistant ol {
|
||||
margin: 8px 0;
|
||||
padding-left: 22px;
|
||||
list-style-type: decimal;
|
||||
}
|
||||
.docs-chat-assistant li {
|
||||
margin: 4px 0;
|
||||
display: list-item;
|
||||
}
|
||||
.docs-chat-assistant strong { font-weight: 600; }
|
||||
.docs-chat-assistant em { font-style: italic; }
|
||||
.docs-chat-assistant h1, .docs-chat-assistant h2, .docs-chat-assistant h3 {
|
||||
font-weight: 600;
|
||||
margin: 12px 0 6px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.docs-chat-assistant h1 { font-size: 1.2em; }
|
||||
.docs-chat-assistant h2 { font-size: 1.1em; }
|
||||
.docs-chat-assistant h3 { font-size: 1.05em; }
|
||||
.docs-chat-assistant blockquote {
|
||||
border-left: 3px solid var(--docs-chat-accent);
|
||||
margin: 10px 0;
|
||||
padding: 4px 12px;
|
||||
color: var(--docs-chat-muted);
|
||||
background: var(--docs-chat-code-bg);
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
.docs-chat-assistant hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: var(--docs-chat-panel-border);
|
||||
margin: 12px 0;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
@@ -182,8 +287,16 @@ html[data-theme="dark"] .docs-chat-assistant { background: rgba(20, 20, 20, 0.7)
|
||||
const inputWrap = document.createElement("div");
|
||||
inputWrap.id = "docs-chat-input";
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.rows = 2;
|
||||
textarea.rows = 1;
|
||||
textarea.placeholder = "Ask about OpenClaw Docs...";
|
||||
|
||||
// Auto-expand textarea as user types (up to max-height set in CSS)
|
||||
const autoExpand = () => {
|
||||
textarea.style.height = "auto";
|
||||
textarea.style.height = Math.min(textarea.scrollHeight, 120) + "px";
|
||||
};
|
||||
textarea.addEventListener("input", autoExpand);
|
||||
|
||||
const send = document.createElement("button");
|
||||
send.id = "docs-chat-send";
|
||||
send.type = "button";
|
||||
@@ -200,12 +313,16 @@ html[data-theme="dark"] .docs-chat-assistant { background: rgba(20, 20, 20, 0.7)
|
||||
root.appendChild(panel);
|
||||
document.body.appendChild(root);
|
||||
|
||||
const addBubble = (text, role) => {
|
||||
const addBubble = (text, role, isMarkdown = false) => {
|
||||
const bubble = document.createElement("div");
|
||||
bubble.className =
|
||||
"docs-chat-bubble " +
|
||||
(role === "user" ? "docs-chat-user" : "docs-chat-assistant");
|
||||
bubble.textContent = text;
|
||||
if (isMarkdown && role === "assistant") {
|
||||
bubble.innerHTML = renderMarkdown(text);
|
||||
} else {
|
||||
bubble.textContent = text;
|
||||
}
|
||||
messages.appendChild(bubble);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
return bubble;
|
||||
@@ -242,9 +359,10 @@ html[data-theme="dark"] .docs-chat-assistant { background: rgba(20, 20, 20, 0.7)
|
||||
const text = textarea.value.trim();
|
||||
if (!text) return;
|
||||
textarea.value = "";
|
||||
textarea.style.height = "auto"; // Reset height after sending
|
||||
addBubble(text, "user");
|
||||
const assistantBubble = addBubble("...", "assistant");
|
||||
assistantBubble.textContent = "";
|
||||
assistantBubble.innerHTML = "";
|
||||
|
||||
try {
|
||||
const response = await fetch(`${apiBase}/chat`, {
|
||||
@@ -253,7 +371,8 @@ html[data-theme="dark"] .docs-chat-assistant { background: rgba(20, 20, 20, 0.7)
|
||||
body: JSON.stringify({ message: text }),
|
||||
});
|
||||
if (!response.body) {
|
||||
assistantBubble.textContent = await response.text();
|
||||
const respText = await response.text();
|
||||
assistantBubble.innerHTML = renderMarkdown(respText);
|
||||
return;
|
||||
}
|
||||
const reader = response.body.getReader();
|
||||
@@ -263,11 +382,12 @@ html[data-theme="dark"] .docs-chat-assistant { background: rgba(20, 20, 20, 0.7)
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
fullText += decoder.decode(value, { stream: true });
|
||||
assistantBubble.textContent = fullText;
|
||||
// Re-render markdown on each chunk for live preview
|
||||
assistantBubble.innerHTML = renderMarkdown(fullText);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
}
|
||||
} catch (err) {
|
||||
assistantBubble.textContent = "Failed to reach docs chat API.";
|
||||
assistantBubble.innerHTML = renderMarkdown("Failed to reach docs chat API.");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user