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