fix(webchat): align demo and widget theme tokens

This commit is contained in:
matevip 2026-05-04 19:23:45 +08:00
parent 1b198e3d08
commit 924d056b79
2 changed files with 155 additions and 22 deletions

119
mateclaw-webchat/index.html Normal file
View File

@ -0,0 +1,119 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MateClaw WebChat Demo</title>
<style>
:root {
--mc-font-body: 'Inter', 'Avenir Next', 'SF Pro Display', 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
--mc-text-sm: 13px;
--mc-text-base: 15px;
--mc-radius-md: 12px;
--mc-radius-full: 9999px;
--mc-primary: #d96d46;
--mc-primary-hover: #bb4f27;
--mc-bg: #f6f1ea;
--mc-bg-elevated: #ffffff;
--mc-bg-muted: #f1e8df;
--mc-border-light: #ebe3db;
--mc-text-primary: #1d1612;
--mc-text-secondary: #665245;
--mc-text-inverse: #ffffff;
--mc-shadow-medium: 0 18px 48px rgba(58, 32, 19, 0.12);
--mc-shadow-strong: 0 24px 70px rgba(58, 32, 19, 0.16);
--mc-chat-bg: #fafaf8;
--mc-chat-header-bg: #ffffff;
--mc-assistant-bubble-bg: #ffffff;
--mc-assistant-bubble-border: #ddd5cc;
--mc-assistant-bubble-color: #1c1410;
--mc-user-bubble-bg: #d97757;
--mc-user-bubble-color: #ffffff;
--mc-input-bg: #fafaf8;
--mc-input-border: #ddd5cc;
--mc-input-text: #1c1410;
}
body {
margin: 0;
min-height: 100vh;
background: var(--mc-bg);
color: var(--mc-text-primary);
font-family: var(--mc-font-body);
}
main {
max-width: 760px;
padding: 48px 32px;
}
h1 {
margin: 0 0 12px;
font-size: 28px;
line-height: 36px;
}
p {
margin: 0;
color: var(--mc-text-secondary);
font-size: 15px;
line-height: 24px;
}
</style>
</head>
<body>
<main>
<h1>MateClaw WebChat Demo</h1>
<p>点击右下角按钮打开 WebChat。本页面会模拟流式接口方便本地预览组件交互。</p>
</main>
<script type="module">
import { init } from './src/index.ts'
const originalFetch = window.fetch.bind(window)
window.fetch = async (input, initOptions) => {
const url = typeof input === 'string' ? input : input.url
if (url.includes('/api/v1/channels/webchat/stream')) {
const body = JSON.parse(String(initOptions?.body || '{}'))
const encoder = new TextEncoder()
return new Response(
new ReadableStream({
start(controller) {
const chunks = [
`data: ${JSON.stringify({ text: `收到:${body.message || ''}` })}\n\n`,
`data: ${JSON.stringify({ text: '\\n这是本地 demo 的模拟流式回复。' })}\n\n`,
'event: done\n\n',
]
chunks.forEach((chunk, index) => {
setTimeout(() => {
controller.enqueue(encoder.encode(chunk))
if (index === chunks.length - 1) controller.close()
}, index * 220)
})
},
}),
{
status: 200,
headers: { 'Content-Type': 'text/event-stream' },
},
)
}
return originalFetch(input, initOptions)
}
init({
apiKey: 'dev-key',
server: location.origin,
title: 'MateClaw WebChat',
primaryColor: 'var(--mc-primary, #D97757)',
subtitle: '本地预览',
welcomeMessage: '我是 MateClaw WebChat本页面用于本地开发预览。',
quickPrompts: ['分析这个问题', '给我执行建议', '你能做哪些事情?'],
})
</script>
</body>
</html>

View File

@ -15,7 +15,7 @@ export interface WebChatConfig {
server: string server: string
/** Widget position */ /** Widget position */
position?: 'bottom-right' | 'bottom-left' position?: 'bottom-right' | 'bottom-left'
/** Primary color (hex) */ /** Primary color (CSS color). Defaults to MateClaw UI token. */
primaryColor?: string primaryColor?: string
/** Widget title */ /** Widget title */
title?: string title?: string
@ -30,7 +30,7 @@ interface Message {
const DEFAULT_CONFIG: Partial<WebChatConfig> = { const DEFAULT_CONFIG: Partial<WebChatConfig> = {
position: 'bottom-right', position: 'bottom-right',
primaryColor: '#409eff', primaryColor: 'var(--mc-primary, #D97757)',
title: 'MateClaw', title: 'MateClaw',
placeholder: 'Type a message...', placeholder: 'Type a message...',
} }
@ -58,6 +58,13 @@ function generateId(): string {
function injectStyles() { function injectStyles() {
const style = document.createElement('style') const style = document.createElement('style')
style.textContent = ` style.textContent = `
.mc-webchat-bubble,
.mc-webchat-panel,
.mc-webchat-panel * {
box-sizing: border-box;
font-family: var(--mc-font-body, 'Inter', 'Avenir Next', 'SF Pro Display', 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif);
letter-spacing: 0;
}
.mc-webchat-bubble { .mc-webchat-bubble {
position: fixed; position: fixed;
${config.position === 'bottom-left' ? 'left: 20px' : 'right: 20px'}; ${config.position === 'bottom-left' ? 'left: 20px' : 'right: 20px'};
@ -66,10 +73,10 @@ function injectStyles() {
height: 56px; height: 56px;
border-radius: 50%; border-radius: 50%;
background: ${config.primaryColor}; background: ${config.primaryColor};
color: white; color: var(--mc-text-inverse, #ffffff);
border: none; border: none;
cursor: pointer; cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.15); box-shadow: var(--mc-shadow-medium, 0 18px 48px rgba(58, 32, 19, 0.12));
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -83,29 +90,31 @@ function injectStyles() {
bottom: 88px; bottom: 88px;
width: 380px; width: 380px;
height: 520px; height: 520px;
background: white; background: var(--mc-bg-elevated, #ffffff);
border-radius: 12px; color: var(--mc-text-primary, #1d1612);
box-shadow: 0 8px 32px rgba(0,0,0,0.12); border: 1px solid var(--mc-border-light, #ebe3db);
border-radius: var(--mc-radius-md, 12px);
box-shadow: var(--mc-shadow-strong, 0 24px 70px rgba(58, 32, 19, 0.16));
display: flex; display: flex;
flex-direction: column; flex-direction: column;
z-index: 99999; z-index: 99999;
overflow: hidden; overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
} }
.mc-webchat-header { .mc-webchat-header {
padding: 14px 16px; padding: 14px 16px;
background: ${config.primaryColor}; background: var(--mc-chat-header-bg, #ffffff);
color: white; color: var(--mc-text-primary, #1d1612);
font-weight: 600; font-weight: 600;
font-size: 15px; font-size: 15px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid var(--mc-border-light, #ebe3db);
} }
.mc-webchat-close { .mc-webchat-close {
background: none; background: none;
border: none; border: none;
color: white; color: var(--mc-text-secondary, #665245);
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 18px;
padding: 0 4px; padding: 0 4px;
@ -119,40 +128,45 @@ function injectStyles() {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
background: var(--mc-chat-bg, #FAFAF8);
} }
.mc-webchat-msg { .mc-webchat-msg {
max-width: 85%; max-width: 85%;
padding: 8px 12px; padding: 8px 12px;
border-radius: 12px; border-radius: var(--mc-radius-md, 12px);
font-size: 14px; font-size: var(--mc-text-sm, 13px);
line-height: 1.5; line-height: 1.5;
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
} }
.mc-webchat-msg--user { .mc-webchat-msg--user {
align-self: flex-end; align-self: flex-end;
background: ${config.primaryColor}; background: var(--mc-user-bubble-bg, ${config.primaryColor});
color: white; color: var(--mc-user-bubble-color, #ffffff);
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
} }
.mc-webchat-msg--assistant { .mc-webchat-msg--assistant {
align-self: flex-start; align-self: flex-start;
background: #f0f2f5; background: var(--mc-assistant-bubble-bg, #ffffff);
color: #333; color: var(--mc-assistant-bubble-color, #1C1410);
border: 1px solid var(--mc-assistant-bubble-border, #DDD5CC);
border-bottom-left-radius: 4px; border-bottom-left-radius: 4px;
} }
.mc-webchat-input-area { .mc-webchat-input-area {
padding: 10px 12px; padding: 10px 12px;
border-top: 1px solid #eee; border-top: 1px solid var(--mc-border-light, #ebe3db);
display: flex; display: flex;
gap: 8px; gap: 8px;
background: var(--mc-bg-elevated, #ffffff);
} }
.mc-webchat-input { .mc-webchat-input {
flex: 1; flex: 1;
padding: 8px 12px; padding: 8px 12px;
border: 1px solid #ddd; border: 1px solid var(--mc-input-border, #DDD5CC);
border-radius: 20px; border-radius: var(--mc-radius-full, 9999px);
font-size: 14px; background: var(--mc-input-bg, #FAFAF8);
color: var(--mc-input-text, #1C1410);
font-size: var(--mc-text-sm, 13px);
outline: none; outline: none;
} }
.mc-webchat-input:focus { border-color: ${config.primaryColor}; } .mc-webchat-input:focus { border-color: ${config.primaryColor}; }
@ -161,7 +175,7 @@ function injectStyles() {
height: 36px; height: 36px;
border-radius: 50%; border-radius: 50%;
background: ${config.primaryColor}; background: ${config.primaryColor};
color: white; color: var(--mc-text-inverse, #ffffff);
border: none; border: none;
cursor: pointer; cursor: pointer;
display: flex; display: flex;