fix(chat): 修复暗黑模式下 Markdown 和滚动条样式 — 响应式 useIsDark hook + 动态主题切换
This commit is contained in:
@@ -6,6 +6,7 @@ import "overlayscrollbars/styles/overlayscrollbars.css";
|
||||
import { OverlayScrollbarsComponent, type OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { useIsDark } from "../../../../hooks/use-is-dark";
|
||||
import { useChatScroll } from "./use-chat-scroll";
|
||||
|
||||
interface ChatScrollAreaProps {
|
||||
@@ -18,6 +19,7 @@ export function ChatScrollArea({ children, messages, status }: ChatScrollAreaPro
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const osRef = useRef<OverlayScrollbarsComponentRef>(null);
|
||||
const [viewportElement, setViewportElement] = useState<HTMLDivElement | null>(null);
|
||||
const isDark = useIsDark();
|
||||
|
||||
const handleOsInitialized = useCallback(() => {
|
||||
const os = osRef.current;
|
||||
@@ -38,7 +40,10 @@ export function ChatScrollArea({ children, messages, status }: ChatScrollAreaPro
|
||||
events={{ initialized: handleOsInitialized }}
|
||||
options={{
|
||||
overflow: { x: "hidden", y: "scroll" },
|
||||
scrollbars: { autoHide: "move", theme: "os-theme-custom" },
|
||||
scrollbars: {
|
||||
autoHide: "move",
|
||||
theme: isDark ? "os-theme-custom-dark" : "os-theme-custom",
|
||||
},
|
||||
}}
|
||||
ref={osRef}
|
||||
>
|
||||
|
||||
@@ -2,6 +2,27 @@ import { CopyOutlined } from "@ant-design/icons";
|
||||
import { CodeHighlighter } from "@ant-design/x";
|
||||
import { App, Button, Flex, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
|
||||
import { useIsDark } from "../../../../../hooks/use-is-dark";
|
||||
|
||||
type SyntaxTheme = Record<string, Record<string, null | number | string>>;
|
||||
|
||||
const customOneDark: SyntaxTheme = {
|
||||
...(oneDark as SyntaxTheme),
|
||||
'pre[class*="language-"]': {
|
||||
...(oneDark as SyntaxTheme)['pre[class*="language-"]'],
|
||||
margin: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const customOneLight: SyntaxTheme = {
|
||||
...(oneLight as SyntaxTheme),
|
||||
'pre[class*="language-"]': {
|
||||
...(oneLight as SyntaxTheme)['pre[class*="language-"]'],
|
||||
margin: 0,
|
||||
},
|
||||
};
|
||||
|
||||
interface CodeBlockWithCopyProps {
|
||||
block?: boolean;
|
||||
@@ -13,14 +34,14 @@ interface CodeBlockWithCopyProps {
|
||||
|
||||
export function CodeBlockWithCopy({ block, children, className, lang }: CodeBlockWithCopyProps) {
|
||||
const { message } = App.useApp();
|
||||
const isDark = useIsDark();
|
||||
|
||||
if (!block) {
|
||||
return <code className={className}>{children}</code>;
|
||||
}
|
||||
|
||||
const codeText = extractText(children);
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
const displayLang = lang || "plaintext";
|
||||
const displayLang = lang ?? "plaintext";
|
||||
|
||||
const handleCopy = () => {
|
||||
void navigator.clipboard.writeText(codeText).then(() => {
|
||||
@@ -44,7 +65,11 @@ export function CodeBlockWithCopy({ block, children, className, lang }: CodeBloc
|
||||
);
|
||||
|
||||
return (
|
||||
<CodeHighlighter header={header} lang={displayLang}>
|
||||
<CodeHighlighter
|
||||
header={header}
|
||||
highlightProps={{ style: (isDark ? customOneDark : customOneLight) as React.CSSProperties }}
|
||||
lang={displayLang}
|
||||
>
|
||||
{codeText}
|
||||
</CodeHighlighter>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { XMarkdown } from "@ant-design/x-markdown";
|
||||
import "@ant-design/x-markdown/themes/dark.css";
|
||||
import "@ant-design/x-markdown/themes/light.css";
|
||||
import { Typography } from "antd";
|
||||
|
||||
import type { PartProps } from "./types";
|
||||
|
||||
import { useIsDark } from "../../../../../hooks/use-is-dark";
|
||||
import { CodeBlockWithCopy } from "./CodeBlockWithCopy";
|
||||
|
||||
interface TextPartProps extends PartProps {
|
||||
@@ -18,6 +20,7 @@ const xmarkdownComponents = {
|
||||
|
||||
export function TextPart({ isStreaming, part, role }: TextPartProps) {
|
||||
const text = typeof part["text"] === "string" ? part["text"] : "";
|
||||
const isDark = useIsDark();
|
||||
|
||||
return (
|
||||
<div className="part-body">
|
||||
@@ -25,7 +28,7 @@ export function TextPart({ isStreaming, part, role }: TextPartProps) {
|
||||
<Typography.Paragraph className="message-body-text">{text}</Typography.Paragraph>
|
||||
) : (
|
||||
<XMarkdown
|
||||
className="x-markdown-light"
|
||||
className={isDark ? "x-markdown-dark" : "x-markdown-light"}
|
||||
components={xmarkdownComponents}
|
||||
content={text}
|
||||
streaming={{ hasNextChunk: isStreaming }}
|
||||
|
||||
Reference in New Issue
Block a user