正在显示
4 个修改的文件
包含
301 行增加
和
88 行删除
| 1 | import chatStyles from "@/app/components/chat.module.scss"; | 1 | import chatStyles from "@/app/components/chat.module.scss"; | 
| 2 | import homeStyles from "@/app/components/home.module.scss"; | 2 | import homeStyles from "@/app/components/home.module.scss"; | 
| 3 | +import styles from "./bgRemoval.module.scss"; | ||
| 3 | 4 | ||
| 4 | import { WindowContent } from "@/app/components/home"; | 5 | import { WindowContent } from "@/app/components/home"; | 
| 5 | import { useMobileScreen } from "@/app/utils"; | 6 | import { useMobileScreen } from "@/app/utils"; | 
| @@ -12,14 +13,17 @@ import { getClientConfig } from "@/app/config/client"; | @@ -12,14 +13,17 @@ import { getClientConfig } from "@/app/config/client"; | ||
| 12 | import React, { useEffect, useMemo, useRef, useState } from "react"; | 13 | import React, { useEffect, useMemo, useRef, useState } from "react"; | 
| 13 | import { useAppConfig } from "@/app/store"; | 14 | import { useAppConfig } from "@/app/store"; | 
| 14 | import { BgSiderBar } from "./bg-siderBar"; | 15 | import { BgSiderBar } from "./bg-siderBar"; | 
| 15 | -import { message } from "antd"; | 16 | +import { Button, Flex, Upload, message } from "antd"; | 
| 17 | +import { UploadOutlined } from "@ant-design/icons"; | ||
| 16 | import type { UploadFile } from "antd"; | 18 | import type { UploadFile } from "antd"; | 
| 17 | 19 | ||
| 18 | import ReturnIcon from "@/app/icons/return.svg"; | 20 | import ReturnIcon from "@/app/icons/return.svg"; | 
| 19 | import MinIcon from "@/app/icons/min.svg"; | 21 | import MinIcon from "@/app/icons/min.svg"; | 
| 20 | import MaxIcon from "@/app/icons/max.svg"; | 22 | import MaxIcon from "@/app/icons/max.svg"; | 
| 21 | import SDIcon from "@/app/icons/sd.svg"; | 23 | import SDIcon from "@/app/icons/sd.svg"; | 
| 22 | -import { ChartComponent } from "../chart"; | 24 | +import LoadingIcon from "@/app/icons/three-dots.svg"; | 
| 25 | +import BotIcon from "@/app/icons/bot.svg"; | ||
| 26 | +import CloseIcon from "@/app/icons/close.svg"; | ||
| 23 | 27 | ||
| 24 | export function BgRemoval() { | 28 | export function BgRemoval() { | 
| 25 | const isMobileScreen = useMobileScreen(); | 29 | const isMobileScreen = useMobileScreen(); | 
| @@ -117,34 +121,48 @@ export function BgRemoval() { | @@ -117,34 +121,48 @@ export function BgRemoval() { | ||
| 117 | </div> | 121 | </div> | 
| 118 | </div> | 122 | </div> | 
| 119 | <div className={chatStyles["chat-body"]} ref={scrollRef}> | 123 | <div className={chatStyles["chat-body"]} ref={scrollRef}> | 
| 120 | - {/* <Flex vertical justify='center' align="center" gap="middle" className={styles['panelFlex']}> | ||
| 121 | - {isLoading ? ( | ||
| 122 | - <div className={clsx("no-dark", styles["loading-content"])}> | ||
| 123 | - <BotIcon /> | ||
| 124 | - <LoadingIcon/> | ||
| 125 | - </div> | ||
| 126 | - ) : previewUrl ? ( | ||
| 127 | - <div className={styles['preview']}> | ||
| 128 | - <img src={previewUrl} alt="Preview" className={styles['previewImage']} /> | ||
| 129 | - <IconButton icon={<CloseIcon />} bordered onClick={closePic} className={styles['icon']} /> | ||
| 130 | - </div> | ||
| 131 | - ) : ( | ||
| 132 | - <Upload | ||
| 133 | - onChange={onChange} | ||
| 134 | - beforeUpload={(file) => { | ||
| 135 | - setFileData(file); | ||
| 136 | - return false; | ||
| 137 | - }} | ||
| 138 | - showUploadList={false} | ||
| 139 | - accept="image/*" | ||
| 140 | - > | ||
| 141 | - <Button icon={<UploadOutlined />} size='large'> | ||
| 142 | - 上传图片 | ||
| 143 | - </Button> | ||
| 144 | - </Upload> | ||
| 145 | - )} | ||
| 146 | - </Flex> */} | ||
| 147 | - <ChartComponent /> | 124 | + <Flex | 
| 125 | + vertical | ||
| 126 | + justify="center" | ||
| 127 | + align="center" | ||
| 128 | + gap="middle" | ||
| 129 | + className={styles["panelFlex"]} | ||
| 130 | + > | ||
| 131 | + {isLoading ? ( | ||
| 132 | + <div className={clsx("no-dark", styles["loading-content"])}> | ||
| 133 | + <BotIcon /> | ||
| 134 | + <LoadingIcon /> | ||
| 135 | + </div> | ||
| 136 | + ) : previewUrl ? ( | ||
| 137 | + <div className={styles["preview"]}> | ||
| 138 | + <img | ||
| 139 | + src={previewUrl} | ||
| 140 | + alt="Preview" | ||
| 141 | + className={styles["previewImage"]} | ||
| 142 | + /> | ||
| 143 | + <IconButton | ||
| 144 | + icon={<CloseIcon />} | ||
| 145 | + bordered | ||
| 146 | + onClick={closePic} | ||
| 147 | + className={styles["icon"]} | ||
| 148 | + /> | ||
| 149 | + </div> | ||
| 150 | + ) : ( | ||
| 151 | + <Upload | ||
| 152 | + onChange={onChange} | ||
| 153 | + beforeUpload={(file) => { | ||
| 154 | + setFileData(file); | ||
| 155 | + return false; | ||
| 156 | + }} | ||
| 157 | + showUploadList={false} | ||
| 158 | + accept="image/*" | ||
| 159 | + > | ||
| 160 | + <Button icon={<UploadOutlined />} size="large"> | ||
| 161 | + 上传图片 | ||
| 162 | + </Button> | ||
| 163 | + </Upload> | ||
| 164 | + )} | ||
| 165 | + </Flex> | ||
| 148 | </div> | 166 | </div> | 
| 149 | </div> | 167 | </div> | 
| 150 | </WindowContent> | 168 | </WindowContent> | 
| 1 | import { Card } from "antd"; | 1 | import { Card } from "antd"; | 
| 2 | import * as echarts from "echarts/core"; | 2 | import * as echarts from "echarts/core"; | 
| 3 | -import { GridComponent, GridComponentOption } from "echarts/components"; | ||
| 4 | -import { BarChart, BarSeriesOption } from "echarts/charts"; | 3 | +import { | 
| 4 | + GridComponent, | ||
| 5 | + TooltipComponent, | ||
| 6 | + LegendComponent, | ||
| 7 | + TitleComponent, | ||
| 8 | +} from "echarts/components"; | ||
| 9 | +import { | ||
| 10 | + BarChart, | ||
| 11 | + PieChart, | ||
| 12 | + LineChart, | ||
| 13 | + BarSeriesOption, | ||
| 14 | + PieSeriesOption, | ||
| 15 | + LineSeriesOption, | ||
| 16 | +} from "echarts/charts"; | ||
| 5 | import { CanvasRenderer } from "echarts/renderers"; | 17 | import { CanvasRenderer } from "echarts/renderers"; | 
| 6 | -import { useRef, useState } from "react"; | ||
| 7 | -const tabList = [ | ||
| 8 | - { | ||
| 9 | - key: "bar", | ||
| 10 | - label: "柱状图", | ||
| 11 | - }, | ||
| 12 | - { | ||
| 13 | - key: "pie", | ||
| 14 | - label: "饼图", | ||
| 15 | - }, | ||
| 16 | - { | ||
| 17 | - key: "line", | ||
| 18 | - label: "折线图", | ||
| 19 | - }, | ||
| 20 | -]; | 18 | +import { useRef, useEffect, useState } from "react"; | 
| 19 | +import { ChartComponentProps, ChartData } from "./getChartData"; | ||
| 20 | + | ||
| 21 | +// 注册必要的组件 | ||
| 22 | +echarts.use([ | ||
| 23 | + GridComponent, | ||
| 24 | + TooltipComponent, | ||
| 25 | + LegendComponent, | ||
| 26 | + TitleComponent, | ||
| 27 | + BarChart, | ||
| 28 | + PieChart, | ||
| 29 | + LineChart, | ||
| 30 | + CanvasRenderer, | ||
| 31 | +]); | ||
| 21 | 32 | ||
| 22 | -echarts.use([GridComponent, BarChart, CanvasRenderer]); | ||
| 23 | type EChartsOption = echarts.ComposeOption< | 33 | type EChartsOption = echarts.ComposeOption< | 
| 24 | - GridComponentOption | BarSeriesOption | 34 | + BarSeriesOption | PieSeriesOption | LineSeriesOption | 
| 25 | >; | 35 | >; | 
| 26 | 36 | ||
| 27 | -var option: EChartsOption; | 37 | +const tabList = [ | 
| 38 | + { key: "bar", label: "柱状图" }, | ||
| 39 | + { key: "pie", label: "饼图" }, | ||
| 40 | + { key: "line", label: "折线图" }, | ||
| 41 | +]; | ||
| 28 | 42 | ||
| 29 | -option = { | ||
| 30 | - xAxis: { | ||
| 31 | - type: "category", | ||
| 32 | - data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], | ||
| 33 | - }, | ||
| 34 | - yAxis: { | ||
| 35 | - type: "value", | ||
| 36 | - }, | ||
| 37 | - series: [ | ||
| 38 | - { | ||
| 39 | - data: [120, 200, 150, 80, 70, 110, 130], | ||
| 40 | - type: "bar", | ||
| 41 | - }, | ||
| 42 | - ], | ||
| 43 | -}; | ||
| 44 | -export function ChartComponent() { | ||
| 45 | - const [activeTabKey, setActiveTabKey] = useState<string>("bar"); | ||
| 46 | - const line = useRef<HTMLCanvasElement>(null); | ||
| 47 | - const bar = useRef<HTMLCanvasElement>(null); | ||
| 48 | - const pie = useRef<HTMLCanvasElement>(null); | ||
| 49 | - const contentListNoTitle: Record<string, React.ReactNode> = { | ||
| 50 | - bar: <canvas ref={bar}></canvas>, | ||
| 51 | - pie: <canvas ref={pie}></canvas>, | ||
| 52 | - line: <canvas ref={line}></canvas>, | 43 | +// 图表配置生成器 | 
| 44 | +const getOption = (type: string, data: ChartData): EChartsOption => { | ||
| 45 | + const commonOption = { | ||
| 46 | + title: { text: `${tabList.find((t) => t.key === type)?.label}` }, | ||
| 47 | + tooltip: { trigger: "item" }, | ||
| 53 | }; | 48 | }; | 
| 54 | - // var myChart = echarts.init(bar); | 49 | + | 
| 50 | + // 提取数值数组 | ||
| 51 | + const values = data.values.map((item) => item.value); | ||
| 52 | + | ||
| 53 | + switch (type) { | ||
| 54 | + case "bar": | ||
| 55 | + return { | ||
| 56 | + ...commonOption, | ||
| 57 | + xAxis: { type: "category", data: data.categories }, | ||
| 58 | + yAxis: { type: "value" }, | ||
| 59 | + series: [{ data: values, type: "bar" }], | ||
| 60 | + }; | ||
| 61 | + case "pie": | ||
| 62 | + return { | ||
| 63 | + ...commonOption, | ||
| 64 | + series: [ | ||
| 65 | + { | ||
| 66 | + type: "pie", | ||
| 67 | + data: data.categories.map((name, i) => ({ | ||
| 68 | + name, | ||
| 69 | + value: values[i], | ||
| 70 | + })), | ||
| 71 | + radius: "50%", | ||
| 72 | + }, | ||
| 73 | + ], | ||
| 74 | + }; | ||
| 75 | + case "line": | ||
| 76 | + return { | ||
| 77 | + ...commonOption, | ||
| 78 | + xAxis: { type: "category", data: data.categories }, | ||
| 79 | + yAxis: { type: "value" }, | ||
| 80 | + series: [ | ||
| 81 | + { | ||
| 82 | + data: values, | ||
| 83 | + type: "line", | ||
| 84 | + smooth: true, | ||
| 85 | + areaStyle: {}, | ||
| 86 | + }, | ||
| 87 | + ], | ||
| 88 | + }; | ||
| 89 | + default: | ||
| 90 | + return {}; | ||
| 91 | + } | ||
| 92 | +}; | ||
| 93 | + | ||
| 94 | +export function ChartComponent({ | ||
| 95 | + data = { categories: [], values: [] }, | ||
| 96 | +}: ChartComponentProps) { | ||
| 97 | + const [activeTabKey, setActiveTabKey] = useState("bar"); | ||
| 98 | + const chartRef = useRef<HTMLDivElement>(null); | ||
| 99 | + const chartInstance = useRef<echarts.ECharts | null>(null); | ||
| 100 | + | ||
| 101 | + useEffect(() => { | ||
| 102 | + if (!chartRef.current) return; | ||
| 103 | + | ||
| 104 | + // 数据校验 | ||
| 105 | + if ( | ||
| 106 | + !data?.categories?.length || | ||
| 107 | + !data?.values?.length || | ||
| 108 | + data.values.some((item) => typeof item.value !== "number") | ||
| 109 | + ) { | ||
| 110 | + console.warn("Invalid chart data"); | ||
| 111 | + return; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + // 销毁旧实例 | ||
| 115 | + if (chartInstance.current) { | ||
| 116 | + chartInstance.current.dispose(); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + // 初始化新图表 | ||
| 120 | + chartInstance.current = echarts.init(chartRef.current); | ||
| 121 | + chartInstance.current.setOption(getOption(activeTabKey, data)); | ||
| 122 | + | ||
| 123 | + // 窗口resize监听 | ||
| 124 | + const resizeHandler = () => chartInstance.current?.resize(); | ||
| 125 | + window.addEventListener("resize", resizeHandler); | ||
| 126 | + | ||
| 127 | + // 清理函数 | ||
| 128 | + return () => { | ||
| 129 | + window.removeEventListener("resize", resizeHandler); | ||
| 130 | + chartInstance.current?.dispose(); | ||
| 131 | + }; | ||
| 132 | + }, [activeTabKey, data]); | ||
| 133 | + | ||
| 55 | return ( | 134 | return ( | 
| 56 | - <> | ||
| 57 | - <Card | ||
| 58 | - style={{ width: "100%" }} | ||
| 59 | - tabList={tabList} | ||
| 60 | - activeTabKey={activeTabKey} | ||
| 61 | - onTabChange={setActiveTabKey} | ||
| 62 | - tabProps={{ size: "middle" }} | ||
| 63 | - > | ||
| 64 | - {contentListNoTitle[activeTabKey]} | ||
| 65 | - </Card> | ||
| 66 | - </> | 135 | + <Card | 
| 136 | + style={{ width: "100%", minHeight: 400 }} | ||
| 137 | + tabList={tabList} | ||
| 138 | + activeTabKey={activeTabKey} | ||
| 139 | + onTabChange={setActiveTabKey} | ||
| 140 | + tabProps={{ size: "middle" }} | ||
| 141 | + > | ||
| 142 | + <div | ||
| 143 | + ref={chartRef} | ||
| 144 | + style={{ | ||
| 145 | + width: "100%", | ||
| 146 | + height: 400, | ||
| 147 | + minHeight: 400, | ||
| 148 | + }} | ||
| 149 | + /> | ||
| 150 | + </Card> | ||
| 67 | ); | 151 | ); | 
| 68 | } | 152 | } | 
app/components/chart/getChartData.ts
0 → 100644
| 1 | +export interface ValueItem { | ||
| 2 | + value: number; | ||
| 3 | + itemStyle?: { | ||
| 4 | + color: string; | ||
| 5 | + }; | ||
| 6 | +} | ||
| 7 | + | ||
| 8 | +export interface ChartData { | ||
| 9 | + categories: string[]; | ||
| 10 | + values: ValueItem[]; | ||
| 11 | +} | ||
| 12 | +export interface ChartComponentProps { | ||
| 13 | + data: { | ||
| 14 | + categories: string[]; | ||
| 15 | + values: ValueItem[]; | ||
| 16 | + }; | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +export function extractDataFromText(text: string): ChartComponentProps | null { | ||
| 20 | + const startMarkers = ["```javascript", "```json"]; | ||
| 21 | + let dataStartIndex = -1; | ||
| 22 | + let markerLength = 0; | ||
| 23 | + | ||
| 24 | + // 查找有效的数据块起始标记 | ||
| 25 | + for (const marker of startMarkers) { | ||
| 26 | + const index = text.indexOf(marker); | ||
| 27 | + if (index !== -1) { | ||
| 28 | + dataStartIndex = index; | ||
| 29 | + markerLength = marker.length; | ||
| 30 | + break; | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + let parsedData: any = null; | ||
| 35 | + | ||
| 36 | + // 如果找到有效数据块 | ||
| 37 | + if (dataStartIndex !== -1) { | ||
| 38 | + // 查找数据块结束标记 | ||
| 39 | + const dataEndIndex = text.indexOf("```", dataStartIndex + markerLength); | ||
| 40 | + if (dataEndIndex !== -1) { | ||
| 41 | + // 提取并清理数据块内容 | ||
| 42 | + const dataBlock = text | ||
| 43 | + .slice(dataStartIndex + markerLength, dataEndIndex) | ||
| 44 | + .trim() | ||
| 45 | + .replace(/'/g, '"') // 转换单引号为双引号 | ||
| 46 | + .replace(/(\w+)(?=\s*:)/g, '"$1"'); // 为键添加双引号 | ||
| 47 | + | ||
| 48 | + try { | ||
| 49 | + // 尝试安全解析数据块内容 | ||
| 50 | + parsedData = JSON.parse(dataBlock); | ||
| 51 | + } catch (error) { | ||
| 52 | + return null; | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + // 如果没有找到数据块,尝试将整个文本解析为 ChartData | ||
| 58 | + if (!parsedData) { | ||
| 59 | + try { | ||
| 60 | + const potentialData: any = JSON.parse(text); | ||
| 61 | + // 验证解析后的数据是否符合 ChartData 结构 | ||
| 62 | + if ( | ||
| 63 | + potentialData && | ||
| 64 | + Array.isArray(potentialData.categories) && | ||
| 65 | + potentialData.categories.every((c: any) => typeof c === "string") && | ||
| 66 | + Array.isArray(potentialData.values) && | ||
| 67 | + potentialData.values.every( | ||
| 68 | + (v: any) => | ||
| 69 | + typeof v?.value === "number" && | ||
| 70 | + v?.itemStyle && | ||
| 71 | + typeof v.itemStyle?.color === "string", | ||
| 72 | + ) | ||
| 73 | + ) { | ||
| 74 | + parsedData = potentialData; | ||
| 75 | + } | ||
| 76 | + } catch (error) { | ||
| 77 | + return null; // 如果解析失败,返回 null | ||
| 78 | + } | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + // 如果成功解析了数据并且符合 ChartData 类型,返回 | ||
| 82 | + if ( | ||
| 83 | + parsedData && | ||
| 84 | + Array.isArray(parsedData.categories) && | ||
| 85 | + Array.isArray(parsedData.values) && | ||
| 86 | + parsedData.values.every( | ||
| 87 | + (v: any) => | ||
| 88 | + typeof v?.value === "number" && | ||
| 89 | + v?.itemStyle && | ||
| 90 | + typeof v.itemStyle?.color === "string", | ||
| 91 | + ) | ||
| 92 | + ) { | ||
| 93 | + return { data: parsedData as ChartData }; | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + return null; // 如果未满足条件,则返回 null | ||
| 97 | +} | 
| @@ -25,6 +25,9 @@ import { IconButton } from "./button"; | @@ -25,6 +25,9 @@ import { IconButton } from "./button"; | ||
| 25 | import { useAppConfig } from "../store/config"; | 25 | import { useAppConfig } from "../store/config"; | 
| 26 | import clsx from "clsx"; | 26 | import clsx from "clsx"; | 
| 27 | 27 | ||
| 28 | +import { ChartComponentProps, extractDataFromText } from "./chart/getChartData"; | ||
| 29 | +import { ChartComponent } from "./chart"; | ||
| 30 | + | ||
| 28 | export function Mermaid(props: { code: string }) { | 31 | export function Mermaid(props: { code: string }) { | 
| 29 | const ref = useRef<HTMLDivElement>(null); | 32 | const ref = useRef<HTMLDivElement>(null); | 
| 30 | const [hasError, setHasError] = useState(false); | 33 | const [hasError, setHasError] = useState(false); | 
| @@ -79,6 +82,7 @@ export function PreCode(props: { children: any }) { | @@ -79,6 +82,7 @@ export function PreCode(props: { children: any }) { | ||
| 79 | const { height } = useWindowSize(); | 82 | const { height } = useWindowSize(); | 
| 80 | const chatStore = useChatStore(); | 83 | const chatStore = useChatStore(); | 
| 81 | const session = chatStore.currentSession(); | 84 | const session = chatStore.currentSession(); | 
| 85 | + const [chartData, setChartData] = useState<ChartComponentProps | null>(null); | ||
| 82 | 86 | ||
| 83 | const renderArtifacts = useDebouncedCallback(() => { | 87 | const renderArtifacts = useDebouncedCallback(() => { | 
| 84 | if (!ref.current) return; | 88 | if (!ref.current) return; | 
| @@ -102,10 +106,20 @@ export function PreCode(props: { children: any }) { | @@ -102,10 +106,20 @@ export function PreCode(props: { children: any }) { | ||
| 102 | const config = useAppConfig(); | 106 | const config = useAppConfig(); | 
| 103 | const enableArtifacts = | 107 | const enableArtifacts = | 
| 104 | session.mask?.enableArtifacts !== false && config.enableArtifacts; | 108 | session.mask?.enableArtifacts !== false && config.enableArtifacts; | 
| 105 | - | ||
| 106 | //Wrap the paragraph for plain-text | 109 | //Wrap the paragraph for plain-text | 
| 107 | useEffect(() => { | 110 | useEffect(() => { | 
| 108 | if (ref.current) { | 111 | if (ref.current) { | 
| 112 | + const textContent = ref.current.innerText; | ||
| 113 | + if (textContent) { | ||
| 114 | + console.log("有textContent"); | ||
| 115 | + console.log(textContent); | ||
| 116 | + const data = extractDataFromText(textContent); | ||
| 117 | + console.log(data); | ||
| 118 | + if (data) { | ||
| 119 | + console.log("data"); | ||
| 120 | + setChartData(data); | ||
| 121 | + } | ||
| 122 | + } | ||
| 109 | const codeElements = ref.current.querySelectorAll( | 123 | const codeElements = ref.current.querySelectorAll( | 
| 110 | "code", | 124 | "code", | 
| 111 | ) as NodeListOf<HTMLElement>; | 125 | ) as NodeListOf<HTMLElement>; | 
| @@ -156,6 +170,7 @@ export function PreCode(props: { children: any }) { | @@ -156,6 +170,7 @@ export function PreCode(props: { children: any }) { | ||
| 156 | /> | 170 | /> | 
| 157 | </FullScreen> | 171 | </FullScreen> | 
| 158 | )} | 172 | )} | 
| 173 | + {chartData && <ChartComponent data={chartData.data} />} | ||
| 159 | <pre ref={ref}> | 174 | <pre ref={ref}> | 
| 160 | <span | 175 | <span | 
| 161 | className="copy-code-button" | 176 | className="copy-code-button" | 
| @@ -271,7 +286,6 @@ function _MarkDownContent(props: { content: string }) { | @@ -271,7 +286,6 @@ function _MarkDownContent(props: { content: string }) { | ||
| 271 | const escapedContent = useMemo(() => { | 286 | const escapedContent = useMemo(() => { | 
| 272 | return tryWrapHtmlCode(escapeBrackets(props.content)); | 287 | return tryWrapHtmlCode(escapeBrackets(props.content)); | 
| 273 | }, [props.content]); | 288 | }, [props.content]); | 
| 274 | - | ||
| 275 | return ( | 289 | return ( | 
| 276 | <ReactMarkdown | 290 | <ReactMarkdown | 
| 277 | remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]} | 291 | remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]} | 
- 
请 注册 或 登录 后发表评论