<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>流式输出md内容 - 插件懒加载</title>
<link rel="stylesheet" type="text/css" href="../packages/cherry-markdown/dist/cherry-markdown.css">
<style>
:root {
--ai-bg: #f8f9fb;
--ai-surface: #fff;
--ai-accent: #0d68ff;
--ai-muted: #ccc;
--ai-gap: 16px;
--ai-max-width: 780px;
--ai-msg-max-width: 680px;
}
html,
body {
margin: 0;
padding: 0;
overflow: auto;
background: var(--ai-bg);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
color: #222;
}
.ai-chat-wrapper {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
padding: 12px 16px;
box-sizing: border-box;
}
.dialog {
padding: 0 0 0 42px;
width: 100%;
max-width: var(--ai-max-width);
box-sizing: border-box;
margin: 0 auto;
overflow-y: auto;
background: transparent;
}
.one-msg {
display: flex;
gap: 12px;
align-items: flex-start;
margin: 18px 0;
padding: 6px 8px;
width: 100%;
max-width: var(--ai-msg-max-width);
box-sizing: border-box;
}
.j-one-msg {
display: none;
}
.avatar {
width: 40px;
height: 40px;
min-width: 40px;
border-radius: 50%;
border: 1px solid var(--ai-muted);
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 700;
background: var(--ai-surface);
color: #111;
}
.chat-one-msg {
display: block;
width: 100%;
max-width: 100%;
background: var(--ai-surface);
border-radius: 8px;
padding: 10px 12px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
box-sizing: border-box;
}
.controls {
margin: 24px auto 20px;
display: flex;
gap: 12px;
align-items: center;
width: 100%;
max-width: var(--ai-max-width);
box-sizing: border-box;
justify-content: flex-start;
flex-wrap: wrap;
}
.button {
display: inline-flex;
gap: 8px;
align-items: center;
border: 0;
background-color: var(--ai-accent);
color: #fff;
padding: 8px 12px;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(13, 104, 255, 0.16);
cursor: pointer;
font-size: 14px;
}
.button.secondary {
background: #ffffff;
color: #333;
border: 1px solid var(--ai-muted);
box-shadow: none;
}
.button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.status {
margin-left: 6px;
display: inline-flex;
align-items: center;
gap: 4px;
}
.status-input {
width: 18px;
height: 18px;
cursor: pointer;
}
/* 插件选项 - 胶囊样式 */
.plugin-options {
width: 100%;
max-width: var(--ai-max-width);
margin: 0 auto 12px;
padding: 0;
background: transparent;
box-shadow: none;
box-sizing: border-box;
}
.plugin-title {
font-size: 13px;
font-weight: 600;
color: #333;
margin: 0 0 8px 0;
display: flex;
align-items: center;
gap: 6px;
}
.plugin-title-hint {
font-size: 11px;
font-weight: 400;
color: #999;
}
.plugin-list {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.plugin-sep {
width: 1px;
height: 20px;
background: var(--ai-muted);
margin: 0 4px;
}
.plugin-group-label {
font-size: 12px;
color: #999;
margin-right: 2px;
}
.plugin-or {
font-size: 11px;
color: #bbb;
font-style: italic;
}
.plugin-item {
position: relative;
cursor: pointer;
user-select: none;
}
.plugin-item input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.plugin-item label {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
line-height: 1.4;
border: 1px solid var(--ai-muted);
background: var(--ai-surface);
color: #666;
transition: all 0.2s ease;
white-space: nowrap;
}
.plugin-item input:checked+label {
background: #e6f0ff;
border-color: var(--ai-accent);
color: var(--ai-accent);
}
.plugin-item input:disabled+label,
.plugin-item input[disabled]+label {
opacity: 0.5;
pointer-events: none;
}
.plugin-item .status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: transparent;
transition: background 0.3s ease;
flex-shrink: 0;
}
.plugin-item .status-dot.loading {
background: #faad14;
animation: pulse-dot 1s infinite;
}
.plugin-item .status-dot.loaded {
background: #52c41a;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* 消息选择器 */
.msg-picker {
width: 100%;
max-width: var(--ai-max-width);
margin: 0 auto 12px;
box-sizing: border-box;
}
.msg-picker-label {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.msg-picker-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.j-msg-pick-btn:disabled,
.j-msg-pick-btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.custom-input {
margin-top: 10px;
margin-bottom: 10px;
width: 100%;
max-width: var(--ai-max-width);
box-sizing: border-box;
}
.custom-textarea {
width: 100%;
height: 100px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-family: inherit;
font-size: 14px;
resize: vertical;
box-sizing: border-box;
}
.cherry {
min-height: 0;
}
/* Toast 提示 */
.toast-container {
position: fixed;
top: 16px;
right: 16px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.toast {
padding: 10px 16px;
border-radius: 8px;
font-size: 13px;
line-height: 1.5;
color: #fff;
background: #333;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
opacity: 0;
transform: translateX(20px);
transition: opacity 0.3s ease, transform 0.3s ease;
pointer-events: auto;
max-width: 360px;
word-break: break-word;
}
.toast-visible {
opacity: 1;
transform: translateX(0);
}
.toast-error {
background: #ff4d4f;
}
.toast-success {
background: #52c41a;
}
.toast-info {
background: #1890ff;
}
@media (max-width: 720px) {
.ai-chat-wrapper {
padding: 12px;
}
.dialog {
padding: 10px;
}
.controls {
flex-wrap: wrap;
gap: 8px;
}
.plugin-list {
gap: 6px;
}
.plugin-item label {
font-size: 12px;
padding: 5px 10px;
}
}
</style>
<link rel="Shortcut Icon" href="../logo/favicon.ico">
<link rel="Bookmark" href="../logo/favicon.ico">
</head>
<body>
<!-- Toast 提示容器 -->
<div class="toast-container j-toast-container"></div>
<div class="ai-chat-wrapper">
<!-- 插件选项区 -->
<div class="plugin-options">
<div class="plugin-title">
扩展插件 <span class="plugin-title-hint">勾选后点击下方消息按钮即可加载</span>
</div>
<div class="plugin-list">
<div class="plugin-item">
<input type="checkbox" id="plugin-mermaid" class="j-plugin-checkbox" data-plugin="mermaid">
<label for="plugin-mermaid">Mermaid<span class="status-dot j-plugin-status j-status-dot-mermaid"></span></label>
</div>
<span class="plugin-sep"></span>
<div class="plugin-group-label">数学公式(二选一)</div>
<div class="plugin-item">
<input type="checkbox" id="plugin-katex" class="j-plugin-checkbox" data-plugin="katex">
<label for="plugin-katex">KaTeX<span class="status-dot j-plugin-status j-status-dot-katex"></span></label>
</div>
<div class="plugin-or">or</div>
<div class="plugin-item">
<input type="checkbox" id="plugin-mathjax" class="j-plugin-checkbox" data-plugin="mathjax">
<label for="plugin-mathjax">MathJax<span class="status-dot j-plugin-status j-status-dot-mathjax"></span></label>
</div>
<span class="plugin-sep"></span>
<div class="plugin-item">
<input type="checkbox" id="plugin-echarts" class="j-plugin-checkbox" data-plugin="echarts">
<label for="plugin-echarts">ECharts<span class="status-dot j-plugin-status j-status-dot-echarts"></span></label>
</div>
</div>
</div>
<!-- 消息选择区 -->
<div class="msg-picker">
<div class="msg-picker-label">选择示例消息(点击后流式打印)</div>
<div class="msg-picker-list j-msg-picker-list"></div>
</div>
<!-- 控件区 -->
<div class="controls">
<button type="button" class="button secondary j-pause-button" disabled>
暂停流式
</button>
<label class="status">
<input class="j-status-input status-input" type="checkbox" checked> 开启流式适配
</label>
</div>
<!-- 消息模板(隐藏) -->
<div class="one-msg j-one-msg" aria-hidden="true">
<div class="avatar">AI</div>
<div class="chat-one-msg"></div>
</div>
<!-- 渲染区 -->
<div class="dialog j-dialog" role="log" aria-live="polite"></div>
<!-- 自定义输入区 -->
<div class="custom-input">
<textarea class="custom-textarea j-custom-textarea" placeholder="请输入您想要流式打印的Markdown内容..."></textarea>
<button type="button" class="button custom-button j-custom-button">流式打印自定义内容</button>
</div>
</div>
<script src="../packages/cherry-markdown/dist/cherry-markdown.stream.js"></script>
<script type="module">
import { aiChatStreamScenario } from './assets/scripts/ai-chat-stream-demo.js';
aiChatStreamScenario();
</script>
</body>
</html>