chore: render katex in markdown
This commit is contained in:
+4
-1
@@ -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"
|
||||||
|
|||||||
Generated
+399
-108
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,10 @@
|
|||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-t-6 {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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{');
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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']",
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user