mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-07 22:36:02 +00:00
feat(web): Add markdown rendering support to pipeline chat messages with toggle (#1826)
* Initial plan * Add markdown rendering support to pipeline debug dialog messages with toggle button Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * Fix code review feedback: remove conflicting styles and imports Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * perf: styles * fix: websocket message broadcasting cross-contamination between person and group channels --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> Co-authored-by: Junyan Qin <rockchinq@gmail.com>
This commit is contained in:
@@ -154,7 +154,7 @@ export default function PipelineDialog({
|
||||
// 编辑流水线时的对话框
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="overflow-hidden p-0 !max-w-[50rem] h-[75vh] flex">
|
||||
<DialogContent className="overflow-hidden p-0 !max-w-[80vw] h-[75vh] flex">
|
||||
<SidebarProvider className="items-start w-full flex h-full min-h-0">
|
||||
<Sidebar
|
||||
collapsible="none"
|
||||
|
||||
@@ -20,6 +20,13 @@ import { toast } from 'sonner';
|
||||
import AtBadge from './AtBadge';
|
||||
import { WebSocketClient } from '@/app/infra/websocket/WebSocketClient';
|
||||
import ImagePreviewDialog from './ImagePreviewDialog';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||
import '@/styles/github-markdown.css';
|
||||
|
||||
interface DebugDialogProps {
|
||||
open: boolean;
|
||||
@@ -50,7 +57,9 @@ export default function DebugDialog({
|
||||
const [previewImageUrl, setPreviewImageUrl] = useState<string>('');
|
||||
const [showImagePreview, setShowImagePreview] = useState(false);
|
||||
const [quotedMessage, setQuotedMessage] = useState<Message | null>(null);
|
||||
const [hoveredMessageId, setHoveredMessageId] = useState<number | null>(null);
|
||||
const [rawModeMessages, setRawModeMessages] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
@@ -195,6 +204,8 @@ export default function DebugDialog({
|
||||
// 监听 sessionType 和 selectedPipelineId 变化,重新加载消息和连接
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// 清空当前消息,避免显示旧的消息
|
||||
setMessages([]);
|
||||
loadMessages(selectedPipelineId);
|
||||
initWebSocket(selectedPipelineId);
|
||||
}
|
||||
@@ -554,7 +565,111 @@ export default function DebugDialog({
|
||||
return t('bots.earlier');
|
||||
};
|
||||
|
||||
// Generate a unique key for a message
|
||||
const getMessageKey = (message: Message): string => {
|
||||
return `${message.id}-${message.timestamp}`;
|
||||
};
|
||||
|
||||
// Toggle raw mode for a message (by default, messages are in markdown mode)
|
||||
const toggleRawMode = (message: Message) => {
|
||||
const key = getMessageKey(message);
|
||||
setRawModeMessages((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(key)) {
|
||||
newSet.delete(key);
|
||||
} else {
|
||||
newSet.add(key);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
// Check if message has any Plain text content
|
||||
const hasPlainText = (message: Message): boolean => {
|
||||
return message.message_chain.some((c) => c.type === 'Plain');
|
||||
};
|
||||
|
||||
// Extract plain text from message chain
|
||||
const getPlainText = (message: Message): string => {
|
||||
return message.message_chain
|
||||
.filter((c) => c.type === 'Plain')
|
||||
.map((c) => (c as Plain).text)
|
||||
.join('');
|
||||
};
|
||||
|
||||
const renderMessageContent = (message: Message) => {
|
||||
const key = getMessageKey(message);
|
||||
const isRawMode = rawModeMessages.has(key);
|
||||
|
||||
// By default, render with markdown if there's plain text (unless raw mode is enabled)
|
||||
if (!isRawMode && hasPlainText(message)) {
|
||||
const plainText = getPlainText(message);
|
||||
const nonPlainComponents = message.message_chain.filter(
|
||||
(c) => c.type !== 'Plain' && c.type !== 'Source',
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="text-base leading-relaxed align-middle">
|
||||
{/* Render non-Plain components first */}
|
||||
{nonPlainComponents.map((component, index) =>
|
||||
renderMessageComponent(component, index),
|
||||
)}
|
||||
{/* Render Plain text as markdown */}
|
||||
<div className="markdown-body">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[
|
||||
rehypeRaw,
|
||||
rehypeHighlight,
|
||||
rehypeSlug,
|
||||
[
|
||||
rehypeAutolinkHeadings,
|
||||
{
|
||||
behavior: 'wrap',
|
||||
properties: {
|
||||
className: ['anchor'],
|
||||
},
|
||||
},
|
||||
],
|
||||
]}
|
||||
components={{
|
||||
ul: ({ children }) => <ul className="list-disc">{children}</ul>,
|
||||
ol: ({ children }) => (
|
||||
<ol className="list-decimal">{children}</ol>
|
||||
),
|
||||
li: ({ children }) => <li className="ml-4">{children}</li>,
|
||||
img: ({ src, alt, ...props }) => {
|
||||
const imageSrc = src || '';
|
||||
|
||||
if (typeof imageSrc !== 'string') {
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt || ''}
|
||||
className="max-w-full h-auto rounded-lg my-4"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={alt || ''}
|
||||
className="max-w-lg h-auto my-4"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{plainText}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-base leading-relaxed align-middle whitespace-pre-wrap">
|
||||
{message.message_chain.map((component, index) =>
|
||||
@@ -619,68 +734,109 @@ export default function DebugDialog({
|
||||
<div
|
||||
key={message.id + message.timestamp}
|
||||
className={cn(
|
||||
'flex group',
|
||||
'flex',
|
||||
message.role === 'user' ? 'justify-end' : 'justify-start',
|
||||
)}
|
||||
onMouseEnter={() => setHoveredMessageId(message.id)}
|
||||
onMouseLeave={() => setHoveredMessageId(null)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex items-end gap-2',
|
||||
message.role === 'user' ? 'flex-row-reverse' : 'flex-row',
|
||||
'max-w-3xl px-5 py-3 rounded-2xl',
|
||||
message.role === 'user'
|
||||
? 'user-message-bubble bg-blue-100 dark:bg-blue-900 text-gray-900 dark:text-gray-100 rounded-br-none'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-bl-none',
|
||||
)}
|
||||
>
|
||||
{renderMessageContent(message)}
|
||||
<div
|
||||
className={cn(
|
||||
'max-w-md px-5 py-3 rounded-2xl',
|
||||
'text-xs mt-2 flex items-center justify-between gap-2',
|
||||
message.role === 'user'
|
||||
? 'bg-[#2288ee] text-white rounded-br-none'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-bl-none',
|
||||
? 'text-gray-600 dark:text-gray-300'
|
||||
: 'text-gray-500 dark:text-gray-400',
|
||||
)}
|
||||
>
|
||||
{renderMessageContent(message)}
|
||||
<div
|
||||
className={cn(
|
||||
'text-xs mt-2 flex items-center justify-between gap-2',
|
||||
message.role === 'user'
|
||||
? 'text-white/70'
|
||||
: 'text-gray-500 dark:text-gray-400',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>
|
||||
{message.role === 'user'
|
||||
? t('pipelines.debugDialog.userMessage')
|
||||
: t('pipelines.debugDialog.botMessage')}
|
||||
</span>
|
||||
<span className="text-[10px]">
|
||||
{formatTimestamp(getMessageTimestamp(message))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{hoveredMessageId === message.id && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs opacity-0 group-hover:opacity-100 transition-opacity bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 whitespace-nowrap"
|
||||
onClick={() => setQuotedMessage(message)}
|
||||
>
|
||||
<svg
|
||||
className="w-3 h-3 mr-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
{hasPlainText(message) && (
|
||||
<button
|
||||
onClick={() => toggleRawMode(message)}
|
||||
className={cn(
|
||||
'px-1.5 py-0.5 rounded text-[10px] transition-colors',
|
||||
message.role === 'user'
|
||||
? 'hover:bg-blue-200 dark:hover:bg-blue-800'
|
||||
: 'hover:bg-gray-200 dark:hover:bg-gray-700',
|
||||
)}
|
||||
title={
|
||||
rawModeMessages.has(getMessageKey(message))
|
||||
? t('pipelines.debugDialog.showMarkdown')
|
||||
: t('pipelines.debugDialog.showRaw')
|
||||
}
|
||||
>
|
||||
{rawModeMessages.has(getMessageKey(message)) ? (
|
||||
<span className="flex items-center gap-0.5">
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z" />
|
||||
</svg>
|
||||
MD
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-0.5">
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h7"
|
||||
/>
|
||||
</svg>
|
||||
{t('pipelines.debugDialog.showRaw')}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setQuotedMessage(message)}
|
||||
className={cn(
|
||||
'px-1.5 py-0.5 rounded text-[10px] transition-colors flex items-center gap-0.5',
|
||||
message.role === 'user'
|
||||
? 'hover:bg-blue-200 dark:hover:bg-blue-800'
|
||||
: 'hover:bg-gray-200 dark:hover:bg-gray-700',
|
||||
)}
|
||||
title={t('pipelines.debugDialog.reply')}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
|
||||
/>
|
||||
</svg>
|
||||
{t('pipelines.debugDialog.reply')}
|
||||
</Button>
|
||||
)}
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
|
||||
/>
|
||||
</svg>
|
||||
{t('pipelines.debugDialog.reply')}
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-[10px]">
|
||||
{formatTimestamp(getMessageTimestamp(message))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
||||
@@ -8,7 +8,7 @@ import rehypeHighlight from 'rehype-highlight';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||
import { getAPILanguageCode } from '@/i18n/I18nProvider';
|
||||
import './github-markdown.css';
|
||||
import '@/styles/github-markdown.css';
|
||||
|
||||
export default function PluginReadme({
|
||||
pluginAuthor,
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
/* GitHub-style Markdown CSS */
|
||||
.markdown-body {
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
color: var(--color-fg-default);
|
||||
background-color: transparent;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans',
|
||||
Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Hide light theme highlight.js styles in dark mode */
|
||||
.dark .markdown-body .hljs {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Ensure code blocks have proper styling */
|
||||
.markdown-body pre code.hljs {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.markdown-body .octicon {
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 2em;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-size: 1.5em;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-size: 0.85em;
|
||||
color: var(--color-fg-muted);
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
margin: 0 0 16px 0;
|
||||
padding: 0 1em;
|
||||
color: var(--color-fg-muted);
|
||||
border-left: 0.25em solid var(--color-border-default);
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.markdown-body ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.markdown-body ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.markdown-body li + li {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.markdown-body li > p {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body li > p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body li > p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Nested lists */
|
||||
.markdown-body ul ul,
|
||||
.markdown-body ul ol,
|
||||
.markdown-body ol ol,
|
||||
.markdown-body ol ul {
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
.markdown-body ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background-color: var(--color-neutral-muted);
|
||||
border-radius: 6px;
|
||||
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
|
||||
'Liberation Mono', monospace;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: var(--color-canvas-subtle);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.markdown-body pre code {
|
||||
display: inline;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
line-height: inherit;
|
||||
word-wrap: normal;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
color: var(--color-accent-fg);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
display: block;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body table tr {
|
||||
background-color: transparent;
|
||||
border-top: 1px solid var(--color-border-muted);
|
||||
}
|
||||
|
||||
.markdown-body table tr:nth-child(2n) {
|
||||
background-color: var(--color-canvas-subtle);
|
||||
}
|
||||
|
||||
.markdown-body table th,
|
||||
.markdown-body table td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid var(--color-border-default);
|
||||
}
|
||||
|
||||
.markdown-body table th {
|
||||
font-weight: 600;
|
||||
background-color: var(--color-canvas-subtle);
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
max-width: 50%;
|
||||
box-sizing: content-box;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
height: 0.25em;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: var(--color-border-default);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Light theme colors */
|
||||
.markdown-body {
|
||||
--color-fg-default: #1f2328;
|
||||
--color-fg-muted: #656d76;
|
||||
--color-canvas-subtle: #f6f8fa;
|
||||
--color-border-default: #d0d7de;
|
||||
--color-border-muted: #d8dee4;
|
||||
--color-neutral-muted: rgba(175, 184, 193, 0.2);
|
||||
--color-accent-fg: #0969da;
|
||||
}
|
||||
|
||||
/* Dark theme colors */
|
||||
.dark .markdown-body {
|
||||
--color-fg-default: #e6edf3;
|
||||
--color-fg-muted: #8d96a0;
|
||||
--color-canvas-subtle: #161b22;
|
||||
--color-border-default: #30363d;
|
||||
--color-border-muted: #21262d;
|
||||
--color-neutral-muted: rgba(110, 118, 129, 0.4);
|
||||
--color-accent-fg: #4493f8;
|
||||
}
|
||||
|
||||
/* Code highlighting styles */
|
||||
.markdown-body .hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: var(--color-fg-default);
|
||||
}
|
||||
|
||||
/* Light theme syntax highlighting */
|
||||
.markdown-body .hljs-comment,
|
||||
.markdown-body .hljs-quote {
|
||||
color: #6a737d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-keyword,
|
||||
.markdown-body .hljs-selector-tag,
|
||||
.markdown-body .hljs-subst {
|
||||
color: #d73a49;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-number,
|
||||
.markdown-body .hljs-literal,
|
||||
.markdown-body .hljs-variable,
|
||||
.markdown-body .hljs-template-variable,
|
||||
.markdown-body .hljs-tag .hljs-attr {
|
||||
color: #005cc5;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-string,
|
||||
.markdown-body .hljs-doctag {
|
||||
color: #032f62;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-title,
|
||||
.markdown-body .hljs-section,
|
||||
.markdown-body .hljs-selector-id {
|
||||
color: #6f42c1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-type,
|
||||
.markdown-body .hljs-class .hljs-title {
|
||||
color: #6f42c1;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-tag,
|
||||
.markdown-body .hljs-name,
|
||||
.markdown-body .hljs-attribute {
|
||||
color: #22863a;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-regexp,
|
||||
.markdown-body .hljs-link {
|
||||
color: #032f62;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-symbol,
|
||||
.markdown-body .hljs-bullet {
|
||||
color: #e36209;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-built_in,
|
||||
.markdown-body .hljs-builtin-name {
|
||||
color: #005cc5;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-meta {
|
||||
color: #6a737d;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-deletion {
|
||||
background-color: #ffeef0;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-addition {
|
||||
background-color: #e6ffed;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown-body .hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Dark theme syntax highlighting */
|
||||
.dark .markdown-body .hljs-comment,
|
||||
.dark .markdown-body .hljs-quote {
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-keyword,
|
||||
.dark .markdown-body .hljs-selector-tag,
|
||||
.dark .markdown-body .hljs-subst {
|
||||
color: #ff7b72;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-number,
|
||||
.dark .markdown-body .hljs-literal,
|
||||
.dark .markdown-body .hljs-variable,
|
||||
.dark .markdown-body .hljs-template-variable,
|
||||
.dark .markdown-body .hljs-tag .hljs-attr {
|
||||
color: #79c0ff;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-string,
|
||||
.dark .markdown-body .hljs-doctag {
|
||||
color: #a5d6ff;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-title,
|
||||
.dark .markdown-body .hljs-section,
|
||||
.dark .markdown-body .hljs-selector-id {
|
||||
color: #d2a8ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-type,
|
||||
.dark .markdown-body .hljs-class .hljs-title {
|
||||
color: #d2a8ff;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-tag,
|
||||
.dark .markdown-body .hljs-name,
|
||||
.dark .markdown-body .hljs-attribute {
|
||||
color: #7ee787;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-regexp,
|
||||
.dark .markdown-body .hljs-link {
|
||||
color: #a5d6ff;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-symbol,
|
||||
.dark .markdown-body .hljs-bullet {
|
||||
color: #ffa657;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-built_in,
|
||||
.dark .markdown-body .hljs-builtin-name {
|
||||
color: #79c0ff;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-meta {
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-deletion {
|
||||
background-color: rgba(248, 81, 73, 0.15);
|
||||
}
|
||||
|
||||
.dark .markdown-body .hljs-addition {
|
||||
background-color: rgba(46, 160, 67, 0.15);
|
||||
}
|
||||
@@ -149,12 +149,28 @@ export class WebSocketClient {
|
||||
break;
|
||||
|
||||
case 'response':
|
||||
// 检查 session_type 是否匹配 - 如果消息没有 session_type 或者不匹配当前session,都忽略
|
||||
if (!data.session_type || data.session_type !== this.sessionType) {
|
||||
// 忽略不匹配的 session_type 消息
|
||||
console.debug(
|
||||
`忽略不匹配的消息: 当前session=${this.sessionType}, 消息session=${data.session_type}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (data.data) {
|
||||
this.onMessageCallback?.(data.data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'user_message':
|
||||
// 检查 session_type 是否匹配 - 如果消息没有 session_type 或者不匹配当前session,都忽略
|
||||
if (!data.session_type || data.session_type !== this.sessionType) {
|
||||
// 忽略不匹配的 session_type 消息
|
||||
console.debug(
|
||||
`忽略不匹配的用户消息: 当前session=${this.sessionType}, 消息session=${data.session_type}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
// 用户消息广播(包括自己发送的消息)
|
||||
if (data.data) {
|
||||
this.onMessageCallback?.(data.data);
|
||||
|
||||
Reference in New Issue
Block a user