chore: render katex in markdown

This commit is contained in:
jialin
2025-02-18 19:51:22 +08:00
parent ea34431cf7
commit 4c77151877
26 changed files with 693 additions and 208 deletions
+4 -1
View File
@@ -17,6 +17,7 @@
"dependencies": {
"@ant-design/icons": "^5.5.1",
"@ant-design/pro-components": "^2.7.19",
"@braintree/sanitize-url": "^7.1.1",
"@huggingface/gguf": "^0.1.7",
"@huggingface/hub": "^0.15.1",
"@huggingface/tasks": "^0.11.6",
@@ -48,7 +49,6 @@
"lodash": "^4.17.21",
"mammoth": "^1.8.0",
"marked": "^14.1.0",
"marked-katex-extension": "^5.1.4",
"ml-dataset-iris": "^1.2.1",
"ml-pca": "^4.1.1",
"numeral": "^2.0.6",
@@ -64,7 +64,10 @@
"react-hotkeys-hook": "^4.5.0",
"react-markdown": "^9.0.3",
"rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"simplebar-react": "^3.2.6",
"styled-components": "^6.1.15",
"umi-presets-pro": "^2.0.3",
"wavesurfer.js": "^7.8.8",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
+399 -108
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -82,6 +82,10 @@
padding-left: 8px;
}
.m-t-6 {
margin-top: 6px;
}
.flex {
display: flex;
}
+89 -32
View File
@@ -1,6 +1,7 @@
import classNames from 'classnames';
import hljs from 'highlight.js';
import { memo, useMemo } from 'react';
import styled from 'styled-components';
import CopyButton from '../copy-button';
import { escapeHtml } from './utils';
@@ -15,9 +16,64 @@ interface CodeViewerProps {
theme?: 'light' | 'dark';
style?: React.CSSProperties;
}
interface CodeHeaderProps {
copyValue: string;
copyable: boolean;
lang: string;
theme: 'light' | 'dark';
}
const CodeHeaderWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
height: 32px;
padding: 0 12px;
font-size: 12px;
color: var(--ant-color-text-tertiary);
background-color: #fafafa;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
&.dark {
background-color: #383838;
color: rgba(255, 255, 255, 0.65);
}
`;
const Wrapper = styled.div`
border-radius: var(--border-radius-mini);
`;
const CodeHeader: React.FC<CodeHeaderProps> = ({
copyValue,
lang,
theme,
copyable
}) => {
if (!copyable) {
return null;
}
return (
<CodeHeaderWrapper
className={classNames({
dark: theme === 'dark',
light: theme === 'light'
})}
>
<span>{lang}</span>
<CopyButton
text={copyValue}
size="small"
style={{ color: '#abb2bf' }}
></CopyButton>
</CodeHeaderWrapper>
);
};
const CodeViewer: React.FC<CodeViewerProps> = (props) => {
const {
code,
code = '',
copyValue,
lang,
autodetect = true,
@@ -63,38 +119,39 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
}, [code, lang, autodetect, ignoreIllegals]);
return (
<pre
className={classNames(
'code-pre custome-scrollbar custom-scrollbar-horizontal ',
{
dark: props.theme === 'dark',
light: props.theme === 'light',
copyable: copyable
}
)}
style={{
height: height,
...style
}}
>
<code
style={{ minHeight: height }}
className={classNames(highlightedCode.className, {
dark: props.theme === 'dark',
light: props.theme === 'light'
})}
dangerouslySetInnerHTML={{
__html: highlightedCode.value
<Wrapper>
<CodeHeader
copyValue={copyValue || code}
lang={lang}
copyable={copyable}
theme={props.theme || 'light'}
></CodeHeader>
<pre
className={classNames(
'code-pre custome-scrollbar custom-scrollbar-horizontal ',
{
dark: props.theme === 'dark',
light: props.theme === 'light'
}
)}
style={{
marginBottom: 0,
height: height,
...style
}}
></code>
{copyable && (
<CopyButton
text={copyValue || code}
size="small"
style={{ color: '#abb2bf' }}
></CopyButton>
)}
</pre>
>
<code
style={{ minHeight: height }}
className={classNames(highlightedCode.className, {
dark: props.theme === 'dark',
light: props.theme === 'light'
})}
dangerouslySetInnerHTML={{
__html: highlightedCode.value
}}
></code>
</pre>
</Wrapper>
);
};
@@ -41,8 +41,8 @@
.code-pre {
padding-inline: 12px 12px;
border-radius: 0 0 var(--border-radius-mini) var(--border-radius-mini);
position: relative;
border-radius: var(--border-radius-mini);
code {
line-height: 1.5;
@@ -0,0 +1,71 @@
import { sanitizeUrl } from '@braintree/sanitize-url';
import 'katex/dist/katex.min.css';
import React, { useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import rehypeKatex from 'rehype-katex';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import styled from 'styled-components';
import HighlightCode from '../highlight-code';
import './index.less';
import { escapeBrackets, escapeDollarNumber, escapeMhchem } from './utils';
interface FullMarkdownProps {
content: string;
theme?: 'light' | 'dark';
}
const Wrapper = styled.div.attrs(() => ({
className: 'markdown-viewer'
}))`
line-height: 2;
`;
const CodeViewer = (props: any) => {
const { children, className, node, theme, ...rest } = props;
const match = /language-(\w+)/.exec(className || '');
return match ? (
<HighlightCode code={children} lang={match[1]} theme={theme} />
) : (
<code {...rest} className={className}>
{children}
</code>
);
};
const FullMarkdown: React.FC<FullMarkdownProps> = ({
content,
theme = 'light'
}) => {
const escapedContent = useMemo(() => {
return escapeMhchem(escapeBrackets(escapeDollarNumber(content)));
}, [content]);
return (
<Wrapper>
<ReactMarkdown
remarkPlugins={[[remarkMath], remarkGfm]}
rehypePlugins={[rehypeKatex]}
components={{
code(props) {
return <CodeViewer {...props} theme={theme}></CodeViewer>;
},
a: ({ href, children, ...props }) => (
<a
href={sanitizeUrl(href)}
{...props}
target="_blank"
rel="noopener noreferrer"
>
{children}
</a>
)
}}
>
{escapedContent}
</ReactMarkdown>
</Wrapper>
);
};
export default FullMarkdown;
@@ -22,6 +22,7 @@
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: var(--font-size-small);
margin-top: 1em;
}
.hj-wrapper {
+8 -1
View File
@@ -1,4 +1,5 @@
import { EyeOutlined } from '@ant-design/icons';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { Checkbox, Image, Typography } from 'antd';
import { unescape } from 'lodash';
import { TokensList, marked } from 'marked';
@@ -52,7 +53,8 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
}, []);
const generateImgSrc = useCallback(
(src: string | null) => {
(url: string | null) => {
const src = sanitizeUrl(url || '');
if (!src) {
return '';
}
@@ -186,6 +188,11 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
return;
}
const imgs = document.querySelectorAll('.markdown-viewer img');
const links = document.querySelectorAll('.markdown-viewer a');
links.forEach((link) => {
// set target blank for all links
link.setAttribute('target', '_blank');
});
imgs.forEach((img) => {
const src = img.getAttribute('src');
img.setAttribute('src', generateImgSrc(src));
+39
View File
@@ -14,3 +14,42 @@ export const unescape = (str = '') => {
? str.replace(reEscapedHtml, (entity) => htmlUnescapes[entity] || "'")
: str;
};
export function escapeDollarNumber(text: string) {
let escapedText = '';
for (let i = 0; i < text.length; i += 1) {
let char = text[i];
const nextChar = text[i + 1] || ' ';
if (char === '$' && nextChar >= '0' && nextChar <= '9') {
char = '\\$';
}
escapedText += char;
}
return escapedText;
}
export function escapeBrackets(text: string) {
const pattern =
/(```[\S\s]*?```|`.*?`)|\\\[([\S\s]*?[^\\])\\]|\\\((.*?)\\\)/g;
return text.replaceAll(
pattern,
(match, codeBlock, squareBracket, roundBracket) => {
if (codeBlock) {
return codeBlock;
} else if (squareBracket) {
return `$$${squareBracket}$$`;
} else if (roundBracket) {
return `$${roundBracket}$`;
}
return match;
}
);
}
export function escapeMhchem(text: string) {
return text.replaceAll('$\\ce{', '$\\\\ce{').replaceAll('$\\pu{', '$\\\\pu{');
}
+13 -9
View File
@@ -516,15 +516,19 @@ const Models: React.FC<ModelsProps> = ({
);
const getModelInstances = async (row: any, options?: any) => {
const params = {
id: row.id,
page: 1,
perPage: 100
};
const data = await queryModelInstancesList(params, {
token: options?.token
});
return data.items || [];
try {
const params = {
id: row.id,
page: 1,
perPage: 100
};
const data = await queryModelInstancesList(params, {
token: options?.token
});
return data.items || [];
} catch (error) {
return [];
}
};
const generateChildrenRequestAPI = (params: any) => {
+9 -7
View File
@@ -1,19 +1,21 @@
import { request } from '@umijs/max';
export const CHAT_API = '/v1-openai/chat/completions';
export const OPENAI_COMPATIBLE = 'v1-openai';
export const CREAT_IMAGE_API = '/v1-openai/images/generations';
export const EDIT_IMAGE_API = '/v1-openai/images/edits';
export const CHAT_API = `/${OPENAI_COMPATIBLE}/chat/completions`;
export const EMBEDDING_API = '/v1-openai/embeddings';
export const CREAT_IMAGE_API = `/${OPENAI_COMPATIBLE}/images/generations`;
export const EDIT_IMAGE_API = `/${OPENAI_COMPATIBLE}/images/edits`;
export const OPENAI_MODELS = '/v1-openai/models';
export const EMBEDDING_API = `/${OPENAI_COMPATIBLE}/embeddings`;
export const OPENAI_MODELS = `/${OPENAI_COMPATIBLE}/models`;
export const RERANKER_API = '/rerank';
export const AUDIO_TEXT_TO_SPEECH_API = '/v1-openai/audio/speech';
export const AUDIO_TEXT_TO_SPEECH_API = `/${OPENAI_COMPATIBLE}/audio/speech`;
export const AUDIO_SPEECH_TO_TEXT_API = '/v1-openai/audio/transcriptions';
export const AUDIO_SPEECH_TO_TEXT_API = `/${OPENAI_COMPATIBLE}/audio/transcriptions`;
export async function execChatCompletions(params: any) {
return request(`${CHAT_API}`, {
@@ -31,7 +31,7 @@ import {
useState
} from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { handleEmbedding } from '../apis';
import { EMBEDDING_API, handleEmbedding } from '../apis';
import { ParamsSchema } from '../config/types';
import '../style/ground-left.less';
import '../style/rerank.less';
@@ -128,7 +128,7 @@ const GroundEmbedding: React.FC<MessageProps> = forwardRef((props, ref) => {
const viewCodeContent = useMemo(() => {
return generateEmbeddingCode({
api: '/v1-openai/embeddings',
api: EMBEDDING_API,
parameters: {
...parameters,
input: [
@@ -236,7 +236,7 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
const viewCodeContent = useMemo(() => {
if (isOpenaiCompatible) {
return generateOpenaiImageCode({
api: '/v1-openai/images/generations',
api: CREAT_IMAGE_API,
parameters: {
...finalParameters,
prompt: currentPrompt
@@ -244,7 +244,7 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
});
}
return generateImageCode({
api: '/v1-openai/images/generations',
api: CREAT_IMAGE_API,
parameters: {
...finalParameters,
prompt: currentPrompt
@@ -24,7 +24,7 @@ import {
useRef,
useState
} from 'react';
import { speechToText } from '../apis';
import { AUDIO_SPEECH_TO_TEXT_API, speechToText } from '../apis';
import { SpeechToTextFormat } from '../config';
import { RealtimeParamsConfig as paramsConfig } from '../config/params-config';
import '../style/ground-left.less';
@@ -96,7 +96,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
const viewCodeContent = useMemo(() => {
return speechToTextCode({
api: '/v1-openai/audio/transcriptions',
api: AUDIO_SPEECH_TO_TEXT_API,
parameters: {
...parameters
}
@@ -21,7 +21,7 @@ import {
useRef,
useState
} from 'react';
import { CHAT_API, textToSpeech } from '../apis';
import { AUDIO_TEXT_TO_SPEECH_API, CHAT_API, textToSpeech } from '../apis';
import { TTSParamsConfig as paramsConfig } from '../config/params-config';
import { MessageItem, ParamsSchema } from '../config/types';
import '../style/ground-left.less';
@@ -97,7 +97,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
const viewCodeContent = useMemo(() => {
return TextToSpeechCode({
api: '/v1-openai/audio/speech',
api: AUDIO_TEXT_TO_SPEECH_API,
parameters: {
...parameters,
input: currentPrompt
@@ -1,4 +1,4 @@
import MarkdownViewer from '@/components/markdown-viewer';
import FullMarkdown from '@/components/markdown-viewer/full-markdown';
import { Input } from 'antd';
import classNames from 'classnames';
import _ from 'lodash';
@@ -35,7 +35,9 @@ const MessageBody: React.FC<MessageBodyProps> = ({
thinkerRef.current = new ThinkParser();
}
if (actions?.includes('markdown')) {
return thinkerRef.current.parse(data.content);
const res = thinkerRef.current.parse(data.content);
console.log('markdown parse:', res);
return res;
}
return {
thought: '',
@@ -206,12 +208,9 @@ const MessageBody: React.FC<MessageBodyProps> = ({
{actions?.includes('markdown') ? (
<>
<ThinkContent content={content.thought}></ThinkContent>
<div style={{ paddingInline: 4 }}>
<MarkdownViewer
content={content.result || ''}
theme="light"
/>
</div>
<FullMarkdown
content={`${content.result || ''}`}
></FullMarkdown>
</>
) : (
<Input.TextArea
@@ -18,31 +18,31 @@ class ThinkParser {
if (!this.collecting) {
if (endIndex !== -1 && (startIndex === -1 || endIndex < startIndex)) {
// 1 发现 `</think>`,但之前没有 `<think>`
// `result` + `</think>` 之前的内容作为 `thought`
// 1 Found `</think>`, but there was no `<think>` before:
// Take `result` + the content before `</think>` as `thought`
this.thought =
this.result + chunk.substring(this.lastCheckedIndex, endIndex);
this.result = ''; // **清空 result**
this.lastCheckedIndex = endIndex + 8; // 跳过 `</think>`
this.result = ''; // **clear result**
this.lastCheckedIndex = endIndex + 8; // Skip `</think>`
} else if (startIndex !== -1) {
// 2 发现 `<think>`,进入思考模式:
// 2 Found `<think>`, start thinking mode:
this.result += chunk.substring(this.lastCheckedIndex, startIndex);
this.collecting = true;
this.lastCheckedIndex = startIndex + 7; // 跳过 `<think>`
this.lastCheckedIndex = startIndex + 7; // Skip `<think>`
} else {
// 3 没有 `<think>` 也没有 `</think>`,直接追加到 `result`
// 3 Still in normal mode, append to `result`
this.result += chunk.substring(this.lastCheckedIndex);
this.lastCheckedIndex = chunk.length;
}
} else {
if (endIndex !== -1) {
// 4 发现 `</think>`,结束思考模式:
// 4 Found `</think>`, end thinking mode:
this.thought += chunk.substring(this.lastCheckedIndex, endIndex);
this.collecting = false;
this.lastCheckedIndex = endIndex + 8; // 跳过 `</think>`
this.lastCheckedIndex = endIndex + 8; // Skip `</think>`
} else {
// 5 仍在思考模式中,追加到 `thought`
// 5 Still in thinking mode, append to `thought`
this.thought += chunk.substring(this.lastCheckedIndex);
this.lastCheckedIndex = chunk.length;
}
@@ -5,6 +5,7 @@ import { useIntl } from '@umijs/max';
import { Button, Modal } from 'antd';
import _ from 'lodash';
import React, { useMemo, useState } from 'react';
import { OPENAI_COMPATIBLE } from '../apis';
type ViewModalProps = {
systemMessage?: string;
@@ -46,7 +47,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const intl = useIntl();
const [lang, setLang] = useState(langMap.shell);
const BaseURL = `${window.location.origin}/v1-openai`;
const BaseURL = `${window.location.origin}/${OPENAI_COMPATIBLE}`;
const formatPyParams = (params: any) => {
return _.keys(params).reduce((acc: string, key: string) => {
@@ -69,7 +70,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const printLog = logcommand ? `print(response.${logcommand})` : '';
if (lang === langMap.shell) {
const code = `curl ${window.location.origin}/v1-openai/${api} \\\n-H "Content-Type: application/json" \\\n-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\\n-d '${JSON.stringify(
const code = `curl ${window.location.origin}/${OPENAI_COMPATIBLE}/${api} \\\n-H "Content-Type: application/json" \\\n-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\\n-d '${JSON.stringify(
{
...parameters,
...payload
+2 -1
View File
@@ -1,4 +1,5 @@
import { map } from 'lodash';
import { CREAT_IMAGE_API } from '../apis';
import { MessageItem } from './types';
export const Roles = {
@@ -121,7 +122,7 @@ export const OpenAIViewCode = {
logcommand: 'data[0].b64_json'
},
imageAdvanced: {
api: '/v1-openai/images/generations',
api: `${CREAT_IMAGE_API}`,
clientType: 'images.generate',
logcommand: {
python: "json()['data'][0]['b64_json']",
+5 -4
View File
@@ -1,3 +1,4 @@
import { OPENAI_COMPATIBLE } from '../apis';
import { fomatNodeJsParams, formatCurlArgs, formatPyParams } from './utils';
export const speechToTextCode = ({ api, parameters }: Record<string, any>) => {
@@ -18,7 +19,7 @@ ${formatCurlArgs(parameters, true)}`
from openai import OpenAI\n
audio_file = open("audio.mp3", "rb")
client = OpenAI(
base_url="${host}/v1-openai",
base_url="${host}/${OPENAI_COMPATIBLE}",
api_key="YOUR_GPUSTACK_API_KEY"
)
@@ -43,7 +44,7 @@ const OpenAI = require("openai");
const openai = new OpenAI({
"apiKey": "YOUR_GPUSTACK_API_KEY",
"baseURL": "${host}/v1-openai"
"baseURL": "${host}/${OPENAI_COMPATIBLE}"
});
async function main() {
@@ -77,7 +78,7 @@ from pathlib import Path
from openai import OpenAI\n
output_file_path = Path(__file__).parent / "output.mp3"
client = OpenAI(
base_url="${host}/v1-openai",
base_url="${host}/${OPENAI_COMPATIBLE}",
api_key="YOUR_GPUSTACK_API_KEY"
)
@@ -102,7 +103,7 @@ const ouptFile = path.resolve("./output.mp3");
const openai = new OpenAI({
"apiKey": "YOUR_GPUSTACK_API_KEY",
"baseURL": "${host}/v1-openai"
"baseURL": "${host}/${OPENAI_COMPATIBLE}"
});
async function main() {
+3 -2
View File
@@ -1,3 +1,4 @@
import { OPENAI_COMPATIBLE } from '../apis';
import { fomatNodeJsParams, formatCurlArgs, formatPyParams } from './utils';
export const generateEmbeddingCode = ({
@@ -17,7 +18,7 @@ ${formatCurlArgs(parameters, false)}`.trim();
const pythonCode = `
from openai import OpenAI\n
client = OpenAI(
base_url="${host}/v1-openai",
base_url="${host}/${OPENAI_COMPATIBLE}",
api_key="YOUR_GPUSTACK_API_KEY"
)
@@ -34,7 +35,7 @@ const OpenAI = require("openai");
const openai = new OpenAI({
"apiKey": "YOUR_GPUSTACK_API_KEY",
"baseURL": "${host}/v1-openai"
"baseURL": "${host}/${OPENAI_COMPATIBLE}"
});
async function main() {
+4 -3
View File
@@ -1,4 +1,5 @@
import _ from 'lodash';
import { OPENAI_COMPATIBLE } from '../apis';
import { fomatNodeJsParams, formatCurlArgs, formatPyParams } from './utils';
export const generateImageCode = ({
@@ -44,7 +45,7 @@ print(response.json()['data'][0]['b64_json'])`.trim();
const nodeJsCode = `
const axios = require('axios');
const url = "http://localhost/v1-openai/images/generations";
const url = "${host}/${OPENAI_COMPATIBLE}/images/generations";
const headers = {
"Content-type": "application/json",
"Authorization": "Bearer $\{YOUR_GPUSTACK_API_KEY}"
@@ -92,7 +93,7 @@ ${formatCurlArgs(_.omit(parameters, ['mask', 'image']), isFormdata)}`
const pythonCode = `
from openai import OpenAI\n
client = OpenAI(
base_url="${host}/v1-openai",
base_url="${host}/${OPENAI_COMPATIBLE}",
api_key="YOUR_GPUSTACK_API_KEY"
)
@@ -109,7 +110,7 @@ const OpenAI = require("openai");
const openai = new OpenAI({
"apiKey": "YOUR_GPUSTACK_API_KEY",
"baseURL": "${host}/v1-openai"
"baseURL": "${host}/${OPENAI_COMPATIBLE}"
});
async function main() {
@@ -41,12 +41,13 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
width={700}
styles={{
body: {
height: 550
height: 620
}
}}
footer={null}
>
<Tabs
size="small"
items={items}
activeKey={activeKey}
type="card"
@@ -67,9 +67,10 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
</span>
<WarningOutlined
style={{ color: 'var(--ant-color-warning)' }}
className="font-size-14 m-l-5"
className="font-size-14 m-l-5 m-r-5"
/>
<Button
style={{ padding: 0 }}
type="link"
size="small"
href="https://docs.gpustack.ai/latest/installation/installation-requirements/"
@@ -85,7 +86,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
theme="dark"
lang="bash"
></HighlightCode>
<h3>
<h3 className="m-t-10">
2. {intl.formatMessage({ id: 'resources.worker.add.step2' })}{' '}
<span
className="font-size-12"
@@ -97,7 +98,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
}}
></span>
</h3>
<div className="m-b-20">
<div className="m-b-16">
<Radio.Group
block
options={containerInstallOptions}
@@ -117,8 +118,8 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
}}
></div>
)}
<HighlightCode theme="dark" code={code} lang="sh"></HighlightCode>
<h3 className="m-b-0">
<HighlightCode theme="dark" code={code} lang="bash"></HighlightCode>
<h3 className="m-b-0 m-t-10">
3. {intl.formatMessage({ id: 'resources.worker.add.step3' })}
</h3>
</div>
@@ -18,12 +18,12 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
code={addWorkerGuide.mac.getToken}
theme="dark"
></HighlightCode>
<h4>Windows </h4>
<h4 className="m-t-6">Windows </h4>
<HighlightCode
code={addWorkerGuide.win.getToken}
theme="dark"
></HighlightCode>
<h3>
<h3 className="m-t-10">
2. {intl.formatMessage({ id: 'resources.worker.add.step2' })}{' '}
<span
className="font-size-12"
@@ -43,7 +43,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
})}
theme="dark"
></HighlightCode>
<h4>Windows </h4>
<h4 className="m-t-6">Windows </h4>
<HighlightCode
theme="dark"
code={addWorkerGuide.win.registerWorker({
@@ -51,7 +51,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
token: '${mytoken}'
})}
></HighlightCode>
<h3 className="m-b-0">
<h3 className="m-b-0 m-t-10">
3. {intl.formatMessage({ id: 'resources.worker.add.step3' })}
</h3>
</div>
@@ -5,7 +5,7 @@
flex-direction: column;
gap: 5px;
border-left: 2px solid var(--ant-color-split);
margin-bottom: 20px;
margin-bottom: 16px;
padding-left: 16px;
background: var(--color-fill-sider);
border-radius: 2px;